In my quest to learn C++ I stumbled across the article Writing Copy Constructors and Assignment Operators which proposes a mechanism to avoid code duplication across copy constructors and assignment operators.
To summarise/duplicate the content of that link, the proposed mechanism is:
struct UtilityClass
{
...
UtilityClass(UtilityClass const &rhs)
: data_(new int(*rhs_.data_))
{
// nothing left to do here
}
UtilityClass &operator=(UtilityClass const &rhs)
{
//
// Leaves all the work to the copy constructor.
//
if(this != &rhs)
{
// deconstruct myself
this->UtilityClass::~UtilityClass();
// reconstruct myself by copying from the right hand side.
new(this) UtilityClass(rhs);
}
return *this;
}
...
};
This seems like a nice way to avoid code duplication whilst ensuring "programatic integrity" but needs weighed against the risk of wasting effort freeing-then-allocating nested memory that could, instead, be re-used (as its author points out).
But I'm not familiar with the syntax that lies at its core:
this->UtilityClass::~UtilityClass()
I assume that this is a way to call the object's destructor (to destroy the contents of the object structure) whilst keeping the structure itself. To a C++ newbie, the syntax looks like a strange mixture of an object method and a class method.
Could someone please explain this syntax to me, or point me to a resource which explains it?
How does that call differ from the following?
this->~UtilityClass()
Is this a legitimate call? Does this additionally destroy the object structure (free from heap; pop off the stack)?
TL;DR DO NOT DO THIS.
To answer the specific question:
In this particular example, there's no difference. As explained in the article you link to, there would be a difference if this were a polymorphic base class, with a virtual destructor.
A qualified call:
would specifically call the destructor of this class, not that of the most derived class. So it only destroys the subobject being assigned to, not the entire object.
An unqualified call:
would use virtual dispatch to call the most derived destructor, destroying the complete object.
The article writer claims that the first is what you want, so that you only assign to the base sub-object, leaving the derived parts intact. However, what you actually do overwrite part of the object with a new object of the base type; you've changed the dynamic type, and leaked whatever was in the derived parts of the old object. This is a bad thing to do in any circumstances. You've also introduced an exception issue: if the construction of the new object fails, then the old object is left in an invalid state, and can't even be safely destroyed.
UPDATE: you also have undefined behaviour since, as described in another answer, it's forbidden to use placement-new to create an object on top of (part of) a differently-typed object.
For non-polymorphic types, a good way to write a copy-assignment operator is with the copy-and-swap idiom. That both avoids duplication by reusing the copy-constructor, and provides a strong exception guarantee - if the assignment fails, then the original object is unmodified.
For polymorphic types, copying objects is more involved, and can't generally be done with a simple assignment operator. A common approach is a virtual
clone
function, which each type overrides to dynamically allocate a copy of itself with the correct type.You can decide to how to call the destructor:
TL;DR version: DO NOT FOLLOW ANY ADVICE GIVEN BY THE AUTHOR OF THAT LINK
The link suggests that this technique can be used in a base class as long as a virtual destructor call is not used, because doing so would destruct parts of the derived class, which isn't the responsibility of the base class
operator=
.This line of reasoning totally fails. The technique can never ever be used in a base class. The reason is that the C++ Standard only allows in-place replacement of an object with another object of the exact same type (see section 3.8 of the Standard):
In the original code, both
return *this;
and subsequent use of the object are undefined behavior; they access an object which has been destroyed, not the newly created object.This is a problem in practice as well: the placement-new call will set up a v-table ptr corresponding to the base class, not the correct derived type of the object.
Even for leaf classes (non-base classes) the technique is highly questionable.