Self-copy assignment can occur in situations of varying complexity, but essentially, all self-copy assignments entail some variation of the following:.
| Code Block |
|---|
#include <utility>
struct S { /* ... */ }
void f() {
S s;
s = s; // Self-copy assignment
} |
...
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 (STL) functions. Since objects of STL types are used in contexts where CopyAssignable is required, STL types are required to gracefully handle self-copy assignment.
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <new>
struct S { S(const S &) noexcept; /* ... */ }; // 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;
}
}; |
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <new>
struct S { S(const S &) noexcept; /* ... */ }; // 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 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.
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <new>
#include <utility>
struct S { S(const S &) noexcept; /* ... */ }; // 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;
}
}; |
Compliant Solution (Move and Swap)
This compliant solution uses the same classes S and T from the previous compliant solution, but adds the following public constructor and methods:
| Code Block | ||||
|---|---|---|---|---|
| ||||
T(T &&rhs) { *this = std::move(rhs); }
// ... everything except operator= ..
T& operator=(T &&rhs) noexcept {
using std::swap;
swap(n, rhs.n);
swap(s1, rhs.s1);
return *this;
} |
The copy assignment operator uses std::move() rather than swap() to achieve safe self-assignment and a strong exception guarantee. The move assignment operator uses a move (via the method parameter) and swap.
The move constructor is not strictly necessary, but defining a move constructor along with a move assignment operator is conventional for classes that support move operations.
Note that unlike copy assignment operators, the signature of a move assignment operator accepts a non-const reference to its object with the expectation that the moved-from object will be left in an unspecified, but valid state. Move constructors have the same difference from copy constructors.
Risk Assessment
Allowing a copy assignment operator to corrupt an object could lead to undefined behavior.
Rule | Severity | Likelihood |
|---|
Detectable | Repairable | Priority | Level |
|---|---|---|---|
OOP54-CPP | Low | Probable |
Yes | No |
P4 | L3 |
Automated Detection
Tool | Version | Checker | Description |
|---|
| Astrée |
| dangling_pointer_use | |||||||
| Clang | 9.0 (r361550) | cert-oop54-cpp | Checked by clang-tidy. | ||||||
| CodeSonar |
| IO.DC | Double Close | ||||||
| Helix QAC |
| C++4072, C++4073, C++4075, C++4076 | |||||||
| Klocwork |
| CL.SELF-ASSIGN | |||||||
| Parasoft C/C++test |
| CERT_CPP-OOP54-a | User-provided copy assignment operators shall handle self-assignment | ||||||
| Polyspace Bug Finder |
| CERT C++: OOP54-CPP | Checks for copy assignment operators where self-assignment is not tested (rule partially covered) |
Related Vulnerabilities
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
...
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 |
| 1997] | 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 |
| 2005] | Item 11, "Handle Assignment to Self in operator=" |
| [Meyers |
| 2014] |
...
...