Implementing assignment operator using copy constructor

Many times we are required to define both a copy constructor and an assignment operator (for example: according to the rule of three, if we need one we are likely to need the other). The two will probably share a big portion of their code, if not all of it. The most common solution is to export the code to a private method which will be called from both the constructor and the assignment operator, effectively giving up on initialization list benefits in the constructor, or duplicating code.

A pretty nice way to overcome this is by implementing the assignment operator in terms of the copy constructor. However, I will explain later why this suggested way is not really recommended to use.

Have a look at the proposed implementation:

#include <new.h>

struct A {
    A ();
    A (const A &a);
    virtual A &operator= (const A &a);
    virtual ~A ();
};

A &A::operator= (const A &a) {
    if (this != &a) {
        this->A::~A(); // explicit non-virtual destructor
        new (this) A(a); // placement new
    }
    return *this;
}

A very important point to take notice of in this implementation is that there’s an explicit call to A’s destructor (not through the virtual dispatch mechanism) – why is that so important, you ask? Since otherwise we’d be calling the virtual destructor, thereby destructing the whole possibly polymorphic object (that can be bigger than A), but only constructing a new A object in its place – hazardous!

Why don’t I recommend using this method?

  • It totally interferes with the RAII concept, which relies on normal construction and destruction of its objects.
  • It can be really heavy in terms of runtime and performance, since destructing and constructing an object is likely to take more resources than simply changing the state of an existing object.
  • Destruction and construction are just not what you would expect to happen when you call an assignment operator.
  • Exception safety: if the copy constructor fails and throws an exception, you are left with a living half-way constructed object.

7 thoughts on “Implementing assignment operator using copy constructor

  1. I’d actually favor a virtual destructor in this context. In a scenario like:

    class D : public A
    {

    int iExtraState;

    }
    A a;
    D d;

    d = a;

    In your implementation, the last call destroys and replaces only the A subobject, and leaves d’s extra-state in a state that is most probably inconsistent with it. This can be a hard bug to track.
    Seems much safer to call the virtual destructor and clean the entire object. If you do need to set only the A-subobject state, the reasonable thing to do would be to create a dedicated interface. Assignment is implicitly assumed to leave the object in a consistent state (as was the copied object).

    1. The assignment you provided is not legit, as it attempts to assign a base class object into a derived class object (in many cases there’s no meaning to assigning a smaller object into a bigger one, since we’re dealing with real life objects. What would you expect to happen on assignment of type AmphibicCar = Car?), and in most cases I would treat it as a logical bug that should not be allowed. Also, I’ve stated in the article why calling the virtual destructor is hazardous in many other cases, which are far more common.
      The other way around (assigning the derived class to a base class object) is called object slicing, and is sometimes allowed. Object slicing would work correctly using the implementation I’ve mentioned.

      However, I would like to state that I do not encourage use of this technique in real life code, as I’ve mentioned in the post itself.

      1. You’re right – thanks. But I still fail to see the logic behind, quote, “calling the virtual destructor, thereby destructing the whole possibly polymorphic object (that can be bigger than A), but only constructing a new A object in its place – hazardous!”
        So – (1) The alternative, namely – NOT destroying the entire object, but rather destroying and replacing only it’s A portion – seems *far* more dangerous to me. I cannot see a logical context that requires it, and it is most probable to leave the object in an inconsistent state, and much worse – one that is seemingly valid (non-null pointers, etc.).
        and (2) Indeed, what is the scenario in which the whole-polymorphic-object is bigger than A? (if, as you rightfully point, it cannot be derived from A?)

        1. The most important thing to keep in mind is this: if the object is indeed of a derived class, you have to separate responsibilities – A has to take care of its own data and leave the rest to the deriving class. Here’s a proper example that should sort everything out.

          Suppose you have a class D which is derived from A.
          How would you implement its assignment operator ? You would call the assignment operator of A, and after it add your own extra stuff.
          Now, suppose that A::operator= is implemented using the mentioned technique, and it calls the virtual destructor like you suggest. Then, after invoking A::operator=, the whole object is destructed, and only its A portion is re-constructed. So, what if I had some complex object added in D ? It was destroyed, and now I attempt to call its operator= on an object that is not valid at all (destructed). That’s a major bug.

          To illustrate better, here’s the code:

          struct D : A {
              std::vector m_vec;
              D &operator= (const D &d) {
                  A::operator=(d); // destructed m_vec along with A
                  m_vec = d.m_vec; // but m_vec is destructed
                  return *this;
              }
          };

          The main issue is that invoking the virtual destructor breaks far more than what is re-constructed.

          1. Ok, i can see how both alternatives may bite. Personally, i’d still prefer making assignment non-virtual and using a virtual destructor in base assignment over risking an inconsistent internal state.

          2. The suggested implementation shouldn’t ever leave the object in an incosistent state, because if we’re a part from a bigger object, the bigger assignment operator (from derived) will take care of the extra state data, and if we’re on our own – we’ve done all there is to do.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>