Shallow & deep copy
Shallow copy
- When a class contains a member in which bitwise copy does not copy the associated memory, a shallow copy occurs.
- This happens when the class has a member:
- Raw pointer: Points to memory on the heap. This can be avoided by using
std::unique_ptr&std::shared_ptr - File Handles: A pointer or integer representing an open file
- Network Sockets: A connection to a server.
- Hardware Interfaces:
- A mapped pointer to memory on the GPU
- A buffer handle that points to memory on the GPU
- Raw pointer: Points to memory on the heap. This can be avoided by using
Deep copy
- When a class contains a member in which bitwise copy does not copy the object, we must override the copy constructor to handle the deep copy.
- This will have to be done recursively for all members, otherwise it will still be a shallow copy.
class Animal
{
Species *species;
Animal(const Animal &other) {
/**
* Bitwise copy will copy the pointer, not the associated
* memory.
* Hence, handled the copy of species
*/
species = new Species(*other.species);
}
Animal& operator=(const Animal &other) {
// 1. Self-assignment check
if (this == &other)
return *this;
// 2. Free existing resources
delete species;
// 3. Copy data from 'other' (Deep Copy)
species = new Species(*other.species);
return *this;
}
}
Rule of 7
The rule of 5 can be applied here to help reduce such nuances. In the case of shallow & deep copy, we are intrested in the first 3 (Rule of 3)
- Destructor: Frees resources when it goes out of scope.
- Copy Constructor: Creates a new object by making a deep copy of an existing one
- Copy Assignment Operator: Replaces the contents of an existing object with a copy of another.
- Move Constructor: Transfers ownership of resources from a temporary (rvalue) object to a new object, avoiding expensive copies.
- Move Assignment Operator: Transfers ownership of resources from a temporary object to an existing one.
- Rule of 6: Adds the default constructor. Creates an object from scratch.
- Rule of 7: Adds the swap function. An efficient, non-throwing function to exchange the contents of two objects
The modern approach is Rule of zero. Use standard containers (like std::vector, std::shared_ptr) to avoid writing any special member functions.