Self-copy assignment can occur in situations of varying complexity, but essentially, all self-copy assignments entail some variation of the following:
#include <utility>
struct S { /* ... */ }
void f() {
S s;
s = s; // Self-copy assignment
}
User-provided copy operators must properly handle self-copy assignment.
The postconditions required for copy assignment are specified by the C++ Standard, [utility.arg.requirements], Table 23 [ISO/IEC 14882-2014], which states that for x = y, the value of y is unchanged. When &x == &y, this postcondition translates into the values of both x and y remaining unchanged. A naive implementation of copy assignment could destroy object-local resources in the process of copying resources from the given parameter. If the given parameter is the same object as the local object, the act of destroying object-local resources will invalidate them. The subsequent copy of those resources will be left in an indeterminate state, which violates the postcondition.
A user-provided copy assignment operator must prevent self-copy assignment from leaving the object in an indeterminate state. This can be accomplished by self-assignment tests, copy-and-swap, or other idiomatic design patterns.
The C++ Standard, [copyassignable], specifies that types must ensure that self-copy assignment leave the object in a consistent state when passed to Standard Template Library functions. Since objects of STL types are used in contexts where CopyAssignable is required, STL types are required to gracefully handle self-copy assignment.
Noncompliant Code Example
In this noncompliant code example, the copy assignment operator does not protect against self-copy assignment. If self-copy assignment occurs, this->s1 is deleted, which results in rhs.s1 also being deleted. The invalidated memory for rhs.s1 is then passed into the copy constructor for S, which can result in dereferencing an invalid pointer.
#include <new>
struct S { /* ... */ }; // Has nonthrowing copy constructor
class T {
int n;
S *s1;
public:
T(const T &rhs) : n(rhs.n), s1(rhs.s1 ? new S(*rhs.s1) : nullptr) {}
~T() { delete s1; }
// ...
T& operator=(const T &rhs) {
n = rhs.n;
delete s1;
s1 = new S(*rhs.s1);
return *this;
}
};
Compliant Solution (Self-Test)
This compliant solution guards against self-copy assignment by testing whether the given parameter is the same as this. If self-copy assignment occurs, then operator= does nothing; otherwise, the copy proceeds as in the original example.
#include <new>
struct S { /* ... */ }; // Has nonthrowing copy constructor
class T {
int n;
S *s1;
public:
T(const T &rhs) : n(rhs.n), s1(rhs.s1 ? new S(*rhs.s1) : nullptr) {}
~T() { delete s1; }
// ...
T& operator=(const T &rhs) {
if (this != &rhs) {
n = rhs.n;
delete s1;
try {
s1 = new S(*rhs.s1);
} catch (std::bad_alloc &) {
s1 = nullptr; // For basic exception guarantees
throw;
}
}
return *this;
}
};
Note that this solution does not provide a strong exception guarantee for the copy assignment. Specifically, if an exception is called when evaluating the new expression, this has already been modified. However, this solution does provide a basic exception guarantee because no resources are leaked and all data members contain valid values. Consequently this code complies with ERR56-CPP. Guarantee exception safety.
Compliant Solution (Copy and Swap)
This compliant solution avoids self-copy assignment by constructing a temporary object from rhs that is then swapped with *this. This compliant solution provides a strong exception guarantee because swap() will never be called if resource allocation results in an exception being thrown while creating the temporary object.
#include <new>
#include <utility>
struct S { /* ... */ }; // Has nonthrowing copy constructor
class T {
int n;
S *s1;
public:
T(const T &rhs) : n(rhs.n), s1(rhs.s1 ? new S(*rhs.s1) : nullptr) {}
~T() { delete s1; }
// ...
void swap(T &rhs) noexcept {
using std::swap;
swap(n, rhs.n);
swap(s1, rhs.s1);
}
T& operator=(const T &rhs) noexcept {
T(rhs).swap(*this);
return *this;
}
};
Risk Assessment
Allowing a copy assignment operator to corrupt an object could lead to undefined behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
OOP54-CPP | Low | Probable | High | P2 | L3 |
Automated Detection
Tool | Version | Checker | Description |
|---|---|---|---|
| Parasoft C/C++test | 9.5 | OOP-34 | |
| PRQA QA-C++ | 4.4 | 4072, 4073, 4075, 4076 |
Related Vulnerabilities
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
This rule is a partial subset of OOP58-CPP. Copy operations must not mutate the source object when copy operations do not gracefully handle self-copy assignment, because the copy operation may mutate both the source and destination objects (due to them being the same object).
Bibliography
| [Henricson 97] | Rule 5.12, Copy assignment operators should be protected from doing destructive actions if an object is assigned to itself |
| [ISO/IEC 14882-2014] | Subclause 17.6.3.1, "Template Argument Requirements" Subclause 17.6.4.9, "Function Arguments" |
| [Meyers 05] | Item 11, "Handle Assignment to Self in operator=" |
| [Meyers 14] |


