Self-assignment can occur in situations of varying complexity, but essentially, all self-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-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-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-assignment by testing whether the given parameter is the same as this
. If self-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 can provide 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-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] |