Shallow copy? Deep copy? Will I drown? - Part 2Filed Under: Weekly Tuesday Dose of goodness
- Shallow copy? Deep copy? Will I drown?
- Shallow copy? Deep copy? Will I drown? - Part 2
- Shallow/Deep copy by design
Dear all,
This week’s post comes a little late since I’m still extremely tied up with matters all over the place. Anyway, I’m going to continue where I left off based on the previous article.
Here goes…
Continuation from the previous post:
Based on our previous post, I’ve talked about several scenarios that will lead you to drown in the deep waters of shallow/deep copy.
In this article, we’ll continue from where we left off and will end off with certain things to look out for whenever you deal with the copy constructor and the canonical assignment operator.
3) No copy constructor, has assignment operator
class Object
{
private:
int* ptr;
int a;
int b;
public:
const Object& operator = (const Object& parmCopy);
Object() : ptr(new int(100)), a(0), b(0) { };
~Object() { delete(ptr); ptr=0; };
};
const Object& Object::operator = (const Object& parmCopy)
{
//Make sure you divorce your heap attachments
//Before taking on a new "wife"
if(this->ptr) delete(ptr); ptr=NULL;
//Deep copy the pointer
if(parmCopy.ptr)
this->ptr = new int( *parmCopy.ptr );
this->a = parmCopy.a;
this->b = parmCopy.b;
return *this;
}
Without a copy constructor, the class ends up behaving in a strange manner. How so? We know that we have to perform certain operations in order to deep copy the pointer ptr over properly.
Of course in this example, the deletion of ptr isn’t necessary. However, if ptr is of a complex type, such as an array, you’ll need to perform a deletion loop before copying the contents over.
Since the assignment operator is doing its job, without the default copy constructor, the object performs shallow copy when the following happens:
Object a; Object b = a; //without a copy ctor, it's doing shallow copy and will crash //when both objects are destroyed Object a; Object b; a = b; //triggers the assignment operator, thus performing a deep copy instead
As you can see, with such inconsistent behavior, it’s not that hard to imagine how much time will be required to actually figure it all out.
4) Has both but does its job carelessly
class Object
{
private:
int* ptr;
int a;
int b;
public:
const Object& operator = (const Object& parmCopy)
{ this->ptr = parmCopy.ptr; this->a = parmCopy.a; this->b = parmCopy.b; };
Object(const Object& parmCopy) : ptr(parmCopy.ptr), a(parmCopy.a), b(parmCopy.b){};
Object() : ptr(new int(100)), a(0), b(0) { };
~Object() { delete(ptr); ptr=0; };
};
As you can see, both the assignment operator and copy constructor was overriden to simply perform a left to right assignment. Obviously, this piece of code will make you drown once both object’s destructors are called.
It’s as good as not overriding them in the first place.
5) Has both but does checks to ensure that 2 objects will be uniquely different
class Object
{
private:
int* ptr;
int a;
int b;
public:
const Object& operator = (const Object& parmCopy)
{ this->ptr = parmCopy.ptr; this->a = parmCopy.a; this->b = parmCopy.b; };
Object(const Object& parmCopy) : ptr(parmCopy.ptr), a(parmCopy.a), b(parmCopy.b){};
Object() : ptr(new int(100)), a(0), b(0) { };
~Object() { delete(ptr); ptr=0; };
};
//Canonical assignment operator
const Object& Object::operator = (const Object& parmCopy)
{
//guard against self-assignment
if(this == &parmCopy)
return this;
//Make sure you divorce your heap attachments
//Before taking on a new "wife"
if(this->ptr) delete(ptr); ptr=NULL;
//Deep copy the pointer
if(parmCopy.ptr)
this->ptr = new int( *parmCopy.ptr );
this->a = parmCopy.a;
this->b = parmCopy.b;
return *this;
}
//Copy constructor
Object::Object(const Object& parmCopy)
:
ptr(new int(0)),
a(parmCopy.a),
b(parmCopy.b)
{
//assign the value over only when the parmCopy.ptr is checked
if(parmCopy.ptr)
*ptr = *parmCopy.ptr;
}
What’re the problems associated with assignment operators?
Many a time, you’ve heard me using the word Canonical Assignment Operator. So what’s canonical?
This word comes from the word - canon . In another words, this also means the standard assignment operator.
A canonical assignment operator takes in a constant reference of its own type and returns a constant reference of itself. Thus allowing the callers to chain assignments together. For example:
Object a, b, c, d, e;
a = b = c = d = e;
This is probably useful only if you have a proper use for it. Otherwise, it’s purely just canonical. Many a time I myself do not declare and definite canonical assignment operators. The only difference is that instead of returning a constant reference, I do not return anything.
The problem with assignment operators is that, there’s a change of hands going on whenever the assignment operator is called.
1) Assignment operators only work directly on stack-allocation objects
2) You have to guard against self-assignment (ie, I assign myself to my current object)
What I mean by work directly? It means without having to dereference or type cast, you’re able to just perform a simple:
a = b;
For pointers, you’ll need to dereference them in order to trigger the LHS’ assignment operator.
*a = *b;
For references, the first example apply even when we know that the referent cannot be changed.
Next, a bigger problem. That is, when you happen to copy from a somewhat innocent looking reference of the same type, you might face a case where:
a = a;
When this happens, usually it’s silly and shouldn’t cause a problem.
The problem however comes when there’s a need to deallocate resources on the current copy (LHS) before copying the contents of the new copy (RHS) over.
If LHS is the same as RHS, then the deallocation would have deallocated the memory resources on the RHS as well.
When you access and dereference the RHS to copy the values over, it’ll definitely crash since you’re now dereferencing a dangling pointer.
Therefore, in the 5th scenario, there’s a small guard to guard against self-assignment. This will definitely prevent a lot of weird problems that might happen in the future.
Conclusion
Finally, the conclusion. After running through two articles regarding shallow and deep copy, I hope that you’ve understood the importance of them being a part of The Big 3 of C++ and what to look out for when implementing them.
In my next article, I’ll be talking about The Big 3.
In fact, in my humble opinion, it should be The Big 4 instead. So, let’s chill for now and I’ll publish my next article as soon as possible. Since it’s Chinese New Year next week, I might really make a post early or very late.
Therefore, just hang on to your horses for now.
Signing off!
Jeremy
- Permalink
- Admin
- 9 Feb 2010 9:36 PM
- Comments (0)