Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The Self-copy assignment operator must work in the event of self-assignment. The obvious approach to implementing the assignment operator is to delete existing pointers and then assign them to copies of the data members of the new object. This could well lead to self-assignment corrupting the state of the object: the chances are that pointers are deleted and then assigned to themselves. To prevent this, the new object state could be built in local variables before being assigned to the data members. However, this could be very inefficient. A better approach is to test for self-assignment (by testing this against the value of the argument passed to the assignment operator) and then do nothing, and even better approaches may be available.

Non-Compliant Code Example

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
}

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 (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.

Page properties
hiddentrue

This rule used to also cover move operations, but there were enough questions about how to formulate that part of the rule to warrant removing that information. See r58 of the rule for the old content and the comments section for problems. Specifically, because the STL does not require objects to be resilient to self-move, it makes user-defined types that use STL types difficult to work with. It may be that we need to disallow a = std::move(a);, but that may be difficult due to template metaprogramming.

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.

Code Blockcode
bgColor#FFcccc
langcpp
class Widget {
    //#include <new>
 
struct S { S(const S &) noexcept; /* ...
 */ };
 
class ThingyT {
  int n;
  S *s1;
 
public:
  T(const T &rhs) : n(rhs.n), s1(rhs.s1 ? // ...new S(*rhs.s1) : nullptr) {}
  ~T() { delete s1; }
 
  //  Thingy...
 
  T& operator=(const ThingyT &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.

Code Block
bgColor#ccccff
langcpp
#include <new>
 
struct S { S(const S &) noexcept; //* ... */ };
 
class T {
  privateint n;
  S *s1;
 
public:
  T(const T &rhs) : n(rhs.n),  Widget *w;
};
s1(rhs.s1 ? new S(*rhs.s1) : nullptr) {}
  ~T() { delete s1; }

  // ...
Thingy 
  T& Thingy::operator=(const ThingyT & rhs) {
    deleteif w;(this != &rhs) {
      n = rhs.n;
     // delete the current Widget s1;
      try {
    w    s1 = new WidgetS(*rhs.w);  // create a copy of rhs's Widgets1);
      } catch (std::bad_alloc &) {
        s1 = nullptr; // For basic exception guarantees
        throw;
      }
    }
    return *this;
  }
// ...

If this copy assignment operator is used in a self-assignment, the delete operator not only deletes the Widget of the current object, it also deletes rhs's Widget, resulting in the Thingy containing a pointer to a deleted object!

Compliant Solution 1

};

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 objectBetter is to test for self-assignment, and to carry out the work of the assignment only in the case that it is not a self-assignment.

Code Block
bgColor#ccccff
langcpp
Thingy& Thingy::operator=(const Thingy& rhs) {
    if (this != &rhs) {
        delete w;
        w = new Widget(*rhs.w);
    }
    return *this;
}

Compliant Solution 2

Alternatively, a copy to the original resource can be taken and not deleted until the new value has been established:

Code Block
bgColor#ccccff
langcpp
Thingy& Thingy::operator=(const Thingy& rhs)#include <new>
#include <utility>
 
struct S { S(const S &) noexcept; /* ... */ };
 
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 {
    Widget *original = w;
    w = new Widget(*rhs.w);
    delete originalusing std::swap;
    swap(n, rhs.n);
    swap(s1, rhs.s1);
  }
 
  T& operator=(T rhs) noexcept {
    rhs.swap(*this);
    return *this;
  }
};

Compliant Solution (Move and Swap)

This version of the code has the advantage that it is exception-safe: if the call of new throws an exception, the original Thingy (and the Widget that it contains) remains unchanged.

Compliant Solution 3

Alternatively, a resource managing smart pointer (or other resource managing container) may be used to hold the external resourcecompliant solution uses the same classes S and T from the previous compliant solution, but adds the following public constructor and methods:

Code Block
bgColor#ccccff
langcpp
class Thingy  T(T &&rhs) {
 *this =  public:std::move(rhs); }

  // ... everything except operator=  // ...

  T& operator=(T &&rhs) noexcept {
  // compiler-generated copy assignment now correct
    private:using std::swap;
    swap(n, rhs.n);
    swap(s1, rhs.s1);
    std::tr1::shared_ptr<Widget> wreturn *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 constructorsThis version of the code is also exception-safe, because the implementation of shared_ptr is exception-safe.

Risk Assessment

Allowing a copy assignment operator to corrupt an object could lead to undefined behavior.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MEM42

OOP54-CPP

low

Low

probable

Probable

high

High

P2

L3

Automated Detection

Tool

Version

Checker

Description

Astrée

Include Page
Astrée_V
Astrée_V

dangling_pointer_use

Clang
9.0 (r361550)
cert-oop54-cppChecked by clang-tidy.
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V

IO.DC
ALLOC.DF
ALLOC.LEAK
LANG.MEM.NPD
LANG.STRUCT.RC
IO.UAC
ALLOC.UAF

Double Close
Double Free
Leak
Null Pointer Dereference
Redundant Condition
Use After Close
Use After Free

Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C++4072, C++4073, C++4075, C++4076


Klocwork
Include Page
Klocwork_V
Klocwork_V
CL.SELF-ASSIGN
Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V

CERT_CPP-OOP54-a

Check for assignment to self in operator=
Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: OOP54-CPPChecks 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.

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

...

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]


...

Image Added Image Added Image Added.MEM41-CPP. Declare a copy constructor, a copy assignment operator, and a destructor in a class that manages resources      08. Memory Management (MEM)      MEM44-CPP. Do not leak resources when handling exceptions