Shallow Copy & Deep Copy
“It is only shallow people who do not judge by appearances. The true mystery of the world is the visible, not the invisible….”
― Oscar Wilde, The Picture of Dorian Gray
Most of the times something for which is learned deeply is profitable. But always, something for which is performed shallowly is safe.
In this blog, we will learn deeply object copying. And the end of the article we will know we should use the shallow copy mechanism.
Let’s begin with a shallow copy:
When we assign two objects to each other or create an object with another object the copy mechanism performs. And always, copy constructor and copy assignment function comes to mind.
If we have a basic class we don’t need more detail for copying mechanisms. Compiler implicitly creates these functions. (Copy constructor and copy assignment operator)
Let’s take a look with an example:
#include <iostream> using namespace std; namespace shallow { class embeddedWorld { std::string name; public: embeddedWorld(const std::string & n): name { n } {} std::string getName() const { return name; } void setName(const std::string & value) { name = value; } }; } int main() { shallow::embeddedWorld world("shallow world"); shallow::embeddedWorld world2 = world; shallow::embeddedWorld world3(world2); shallow::embeddedWorld world4("Black Hole"); world4 = world3; cout << world.getName() << endl; cout << world2.getName() << endl; cout << world3.getName() << endl; cout << world4.getName() << endl; return 0; }
The output of code:
shallow world
shallow world
shallow world
shallow world
The example, above shows that we can copy objects to the same type of objects with implicitly created copy mechanism functions. We can use copy mechanisms without user-declared copy mechanisms functions. However, if we have a non-class type such (as a raw pointer) as a member of our own class the implicitly created copy functions copy to the pointer shallowly. It means all copied objects will have the same pointer if we have a raw pointer. This causes some memory problems.
For example:
The class, world has a raw pointer for naming the world. We have to delete our raw pointer source inside the user-declared destructor. That’s why we declared the destructor.
#include <iostream> using namespace std; class world { char * name; public: world(const char * n = ""): name(new char[strlen(n) + 1]) { memcpy(name, n, strlen(n)); } ~world() { delete[] name; } char * getName() const { return name; }; void setName(const char * cp) { memcpy(name, cp, std::strlen(cp)); } }; int main() { world w2("Mars"); cout << "w2's name address:" << static_cast < const void * > (w2.getName()) << endl; { world w1("Earth"); w2 = w1; //calls copy assignment function (implicitly generated) cout << "After the copyingness" << endl; cout << "w1's name address:" << static_cast < const void * > (w1.getName()) << endl; cout << "w2's name address:" << static_cast < const void * > (w2.getName()) << endl; } return 0; }
The output of the code:
w2’s name address:0x7fd2a2c057b1
After the copyingness
w1’s name address:0x7fd2a2c057c1
w2’s name address:0x7fd2a2c057c1
The program has unexpectedly finished.
Inside the main function, we created two objects of world-class. (w1 and w2).
The object, w2 is created inside the local brace in the main function. We copy w1 to w2 at the same block. After copying w1 and w2 share the same pointer because of the shallow copy with implicitly created copy assignment function. When w1 goes out of scope it deletes its own raw pointer, name inside the destructor. After, then w2 has deleted the raw pointer. The object w1 doesn’t know it! Of course, this is a basic example to see this memory failure but we have to think of more complicated applications.
To avoid this kind of problem we have some options.
- Using class type instead of a non-class raw pointer. We can use std::string for the char raw pointer in this example. The shallow copy can handle it. No destruction is needed.
- Using smart pointers. We can use std::unique_ptr<char> for this example. No destruction is needed.
- Implementing deep copy. Destruction and copy mechanism functions are needed.
Deep copy means copying each element individually instead of the default copy mechanism. In C++, we can declare the copy constructor and copy assignment operator function to copy deeply.
Deep copy example with the same example above.
#include <iostream> using namespace std; namespace deepCopy { class world { char * name; public: world(const char * n = ""): name(new char[strlen(n) + 1]) { memcpy(name, n, strlen(n)); } world(const world & w) { world(w.name); } world & operator = (const world & w) { if (this == & w) //check self assignment return *this; size_t s = strlen(w.name) + 1; char * alloc = new char[s]; memcpy(alloc, w.name, s); delete[] name; name = alloc; return *this; } ~world() { delete[] name; } char * getName() const { return name; }; void setName(const char * cp) { memcpy(name, cp, std::strlen(cp)); } }; } namespace shallowCopy { class world { char * name; public: world(const char * n = ""): name(new char[strlen(n) + 1]) { memcpy(name, n, strlen(n)); } world(const world & w) = default; world & operator = (const world & w) = default; ~world() { delete[] name; } char * getName() const { return name; }; void setName(const char * cp) { memcpy(name, cp, std::strlen(cp)); } }; } int main() { cout << "****Deep Copy****" << endl; deepCopy::world w1("Mars"); cout << "w1's name address:" << static_cast < const void * > (w1.getName()) << endl; { deepCopy::world w2("Earth"); w1 = w2; //calls copy assignment function (implicitly generated) cout << "After the copyingness" << endl; cout << "w1's name address:" << static_cast < const void * > (w1.getName()) << endl; cout << "w2's name address:" << static_cast < const void * > (w2.getName()) << endl; } cout << "****Shallow Copy****" << endl; shallowCopy::world w3("Mars"); cout << "w3's name address:" << static_cast < const void * > (w3.getName()) << endl; { shallowCopy::world w4("Earth"); w3 = w4; //calls copy assignment function (implicitly generated) cout << "After the copyingness" << endl; cout << "w3's name address:" << static_cast < const void * > (w3.getName()) << endl; cout << "w4's name address:" << static_cast < const void * > (w4.getName()) << endl; } return 0; }
The output of the code:
****Deep Copy****
w1’s name address:0x7f9a844057b0
After the copyingness
w1’s name address:0x7f9a844057d0
w2’s name address:0x7f9a844057c0
****Shallow Copy****
w3’s name address:0x7f9a844057c0
After the copyingness
w3’s name address:0x7f9a844057b0
w4’s name address:0x7f9a844057b0
The program has unexpectedly finished.
Here are the differences between deep copy and shallow copy:
With deep copy, we clone the resource, we don’t use the same memory. But in the shallow copy example, only reference of resource is copied. That’s why the address of the name is the same as we can see in the output of the code. And according to the example, we created a memory leakage.
As it is seen, in the deep copy we do more operation than the shallow copy functions. That’s why deep copy is slower than shallow copy.
Summary:
We always have to ask ourselves why we need deep operations. Simplifying is the best ever. We can use class members instead of the raw pointer. We can use also smart pointers too.
In C++, the simplifying of this issue is named The rule of 0.
It means: If you can avoid defining default operations, do.