...
| 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;
}
};
|
...
| 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] |
...
...