Rule Definition
The copy assignment operator lets you create a new object from an existing object through initialization. A copy assignment operator of a class A is a non-static, non-template member function that has one of the following forms:
- 'A::operator=(A)'
- 'A::operator=(A&) // This is the most common form'
- 'A::operator=(const A&)'
- 'A::operator=(volatile A&)'
- 'A::operator=(const volatile A&)'
If you do not declare a copy assignment operator for a class A, the compiler will implicitly declare one for you which will be public.
Assignment operators using pointers are also taken into account:
- 'A::operator=(A*)'
- 'A::operator=(const A*)'
- 'A::operator=(volatile A*)'
- 'A::operator=(const volatile A*)'
The problem with these assignments is that abstract classes are used to manipulate instances of concrete derived classes through a common interface. Which means that if you provide a user accessible assignment operator, you will end up:
- Either doing partial assignment and only copying members from the base class
- Or doing mixed-type assignment if you declared the 'operator=' as 'virtual', which is not recommended.
By ensuring that the copy assignment operator is protected, it can only be called by a derived class, for instance when the class wants to implement its own assignment operator.
Note that most of the time, the concrete derived classes will not provide an assignment operator either, since they will be manipulated through pointers to their base classes, and an additional operation might not prove particularly useful.
Remediation
Make the operator protected and non-virtual. Create it if it is not present. Optionally define a non virtual version in the concrete classes.
Violation Code Sample
class Vegetable
{
public:
virtual void f() = 0;
/*virtual*/ Vegetable& operator=(const vegetable & v); // should have protected access
};
class Carrot : public Vegetable
{
private:
double length;
};
class Broccoli : public Vegetable
{
private:
int flowerHeadCount;
};
void foo()
{
Carrot obj1;
Broccoli obj2;
Vegetable* ptr1 = &obj1;
Vegetable* ptr2 = &obj2;
*ptr1 = *ptr2; // problem: partial assignment or mixed type assignment
}
Fixed Code Sample
class Vegetable
{
public:
virtual void f() = 0;
protected:
Vegetable& operator=(const Vegetable &);
};
class Carrot : public Vegetable
{
public:
Carrot& operator=( const Carrot& c);
private:
double length;
};
void foo()
{
Carrot carrot1, carrot2;
Broccoli broccoli1;
Vegetable* ptr1 = &carrot1;
Vegetable* ptr2 = &broccoli1;
*ptr1 = *ptr2; // Compilation error, which is a good thing
carrot2 = carrot1; // Ok
}
Reference
More Effective C++, Item 33
High Integrity C++, Rule 3.3.14
Related Technologies
C++
Technical Criterion
Programming Practices - Unexpected Behavior
About CAST Appmarq
CAST Appmarq is by far the biggest repository of data about real IT systems. It's built on thousands of analyzed applications, made of 35 different technologies, by over 300 business organizations across major verticals. It provides IT Leaders with factual key analytics to let them know if their applications are on track.