Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Copy Constructor and Assignment Operator FAQ, Part II

Last updated Jan 1, 2003.

The final part of this series discusses the benefits of compiler-generated copy constructor and assignment operator, reviews a questionable technique of implementing an assignment operator by means of invoking the copy constructor, and finally, it lists common design mistakes and bugs in the implementation of the copy constructor and the assignment operator.

Question: Can I Implement The Assignment Operator by Means of Invoking The Copy Constructor?

Answer: Yes, you can. However, it isn’t such a great idea. Some C++ gurus, journals and books in the early 1990s advocated the implementation of the assignment operator by means of calling the copy constructor. This idiom seemed like a good example of code reuse and "cuteness". It works like this:

  • The assignment operator checks whether *this and its argument are not identical.
  • If the two objects are not identical, invoke the destructor of *this explicitly
  • Reuse the same memory address of this to copy construct a new object using placement new

The following code listing demonstrates this technique:

C& operator=(const C& rhs)
{
if (this !=&rhs)
{
 this->~C();//destroy self
 new (this) C(rhs);//reconstruct self using placement new and copy ctor
}
return *this;
}

This trick allegedly prevents code reduplication. However, it has some serious flaws. In order to work, C’s destructor must assign NULLify every pointer that it has deleted because the subsequent copy constructor call might delete the same pointers again when it reassigns a new value to char arrays. In addition, assigning objects is usually less expensive than a full-blown copy construction. This technique makes your assignment operator less efficient. The third and perhaps most compelling reason why you should avoid this idiom is its redundancy. Careful design can eliminate the need for writing the copy constructor and assignment operator manually. Let the compiler will generate them automatically!

Question: Can I trust the Compiler-Generated Copy Constructor and Assignment Operator?

Answer: Compiler-generated code is your best friend, provided that you adhere to good style OO practices and know the rules. As explained in part I, the compiler- generated copy constructor and assignment operator perform member-wise copying of user-declared data members. By replacing low-level datatypes -- raw pointers and char arrays for instance -- with their high-level Standard Library counterparts std::tr1::shared_ptr and std::string, not only are you eliminating the onerous bugs associated with manual resource management, you’re also guaranteeing that the compiler-generated copy constructor and assignment operator will do the right thing. Consider the following class:

class Person
{
char * s name;
...
Person();
~Person()
Person(const Person&);
Person& operator=(const Person& rhs);
};

By simply replacing the char * member with a string object, you eliminate the need for handwritten copy constructor and an assignment operator. Design your classes so that they can use the compiler-generated special member functions as much as possible.

Question: Which Common Design Mistakes Occur in User-Written Copy Constructor and Assignment Operator?

Answer: Many things can go wrong when users write these member functions on their own:

  • Forgetting to copy some of the object’s members
  • Using shallow copying instead of deep copying
  • Copying or assigning members in random order, for example copying the third member before the second, when the second depends on the third.

These are usually innocent human mistakes. However, there are also serious design mistakes that exhibit misunderstanding.

Using memcpy(). I’ll say it bluntly: memcpy() has no place in C++ as it is unaware of object semantics. It can be used only with POD types but class objects are rarely POD types anyway. Using sizeof(*this) to calculate the object’s size in a memcpy() call is particularly dangerous as it might overwrite the virtual pointer or do other foolish things with file descriptors, handles and pointers.

That said, the worst design mistake is defining empty copy constructors and assignment operators. When I discussed default constructors, I criticized a similar anti-pattern of defining a constructor that doesn’t do anything:

struct A
{
 A() {} //totally useless though nearly harmless
};

A user-defined constructor that doesn’t do anything is totally useless.As denigrated as this practice is, it’s not as detrimental as defining empty copy constructors and assignment operators, hoping that the compiler will work miracles:

struct S
{
 string name;
 int age;
 bool cached;
 S(string n, int a, bool c):name(n), age(a), cached(c) {}
 S(const S& rhs) {} //disatrous
 S& operator=(const S& rhs) {}//disastrous
};
int main()
{
S s1("Adam Smith", 20, false);
S s2(s1);//s1 members’ values aren’t copied at all! 
cout<<s2.age<<s2.cached<<endl; //garbage values
}

The user-defined copy constructor doesn’t contain any statements that copy rhs members to *this. Therefore, all POD members of s2 will have indeterminate (i.e., garbage) values. The string member will be empty.

When the assignment operator is implemented in the same manner, it will leave the target object intact:

S s1("Adam Smith", 20, false);
S s2("Robert Burns", 30, true);
s2=s1; //no effect on s2 members
cout<<s2.name<<endl; //still Robert Burns

The irony here is that the programmer shouldn’t have declared the copy constructor and assignment operator in the first place! The compiler-generated versions would work just fine.

Question: How Can I Prevent Object-Copying?

Answer: Declare the copy constructor and the assignment operator as private members, without defining them. Any statement that involves direct or indirect copying of that class will cause a compilation error. This technique isn’t exactly the picture of elegance, but in contemporary C++ there’s no other way to block copying inexpensively. When deleted and defaulted functions become available, you’ll be able to use a simpler and more elegant technique.