...
In this noncompliant code example, the copy and move assignment operators do not protect against self-assignment. If self-copy assignment occurs, this->S1>s1 is deleted, which results in RHSrhs.S1s1 also being deleted. The invalidated memory for RHSrhs.S1s1 is then passed into the copy constructor for S, which can result in dereferencing an invalid pointer. If self-move assignment occurs, it is dependent on the implementation of std::swap as to whether S1 s1 is left in a valid but unspecified state.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <new> #include <utility> struct S { /* ... */ }; // Has nonthrowing copy constructor class T { int Nn; S *S1s1; public: T(const T &RHSrhs) : Nn(RHSrhs.Nn), S1s1(RHSrhs.S1s1 ? new S(*RHSrhs.S1s1) : nullptr) {} T(T &&RHSrhs) noexcept : Nn(RHSrhs.Nn), S1s1(RHSrhs.S1s1) { RHSrhs.S1s1 = nullptr; } ~T() { delete S1s1; } // ... T& operator=(const T &RHSrhs) { Nn = RHSrhs.Nn; delete S1s1; S1s1 = new S(*RHSrhs.S1s1); return *this; } T& operator=(T &&RHSrhs) noexcept { Nn = RHSrhs.Nn; S1s1 = RHSrhs.S1s1; return *this; } }; |
Compliant Solution (Self-Test)
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <new> #include <utility> struct S { /* ... */ }; // Has nonthrowing copy constructor class T { int Nn; S *S1s1; public: T(const T &RHSrhs) : Nn(RHSrhs.Nn), S1s1(RHSrhs.S1s1 ? new S(*RHSrhs.S1s1) : nullptr) {} T(T &&RHSrhs) noexcept : Nn(RHSrhs.Nn), S1s1(RHSrhs.S1s1) { RHSrhs.S1s1 = nullptr; } ~T() { delete S1s1; } // ... T& operator=(const T &RHSrhs) { if (this != &RHSrhs) { Nn = RHSrhs.Nn; delete S1s1; try { S1s1 = new S(*RHSrhs.S1s1); } catch (std::bad_alloc &) { S1s1 = nullptr; // For basic exception guarantees throw; } } return *this; } T& operator=(T &&RHSrhs) noexcept { if (this != &RHSrhs) { Nn = RHSrhs.Nn; S1s1 = RHSrhs.S1s1; } 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.
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <new>
#include <utility>
struct S { /* ... */ }; // Has nonthrowing copy constructor
class T {
int Nn;
S *S1s1;
public:
T(const T &RHSrhs) : Nn(RHSrhs.Nn), S1s1(RHSrhs.S1s1 ? new S(*RHSrhs.S1s1) : nullptr) {}
T(T &&RHSrhs) noexcept : Nn(RHSrhs.Nn), S1s1(RHSrhs.S1s1) { RHSrhs.S1s1 = nullptr; }
~T() { delete S1s1; }
// ...
void swap(T &RHSrhs) noexcept {
using std::swap;
swap(Nn, RHSrhs.Nn);
swap(S1s1, RHSrhs.S1s1);
}
T& operator=(const T &RHSrhs) noexcept {
T(RHSrhs).swap(*this);
return *this;
}
T& operator=(T &&RHSrhs) noexcept {
if (&RHSrhs != this) {
Nn = RHSrhs.Nn;
S1s1 = RHSrhs.S1s1;
}
return *this;
}
}; |
Exceptions
...