...
For example, in C++03, std::auto_ptr
had the following copy operation signatures [ISO/IEC 14882-2003]:
Copy constructor | auto_ptr(auto_ptr &A); |
Copy assignment | auto_ptr& operator=(auto_ptr &A); |
Both copy construction and copy assignment would mutate the source argument, A
, by effectively calling this->reset(A.release())
. However, this invalidated assumptions made by standard library algorithms such as std::sort()
, which may need to make a copy of an object for later comparisons [Hinnant 05]. Consider an implementation the following implementation of std::sort()
that implements the quick sort algorithm:.
Code Block |
---|
// ... value_type pivot_element = *mid_point; // ... |
At this point, the sorting algorithm assumes that pivot_element
and *mid_point
have equivalent value representations and will compare equal. However, for std::auto_ptr
, this is not the case because *mid_point
has been mutated, and mutated and results in unexpected behavior.
In C++11, the std::unique_ptr
smart pointer class was introduced as a replacement for std::auto_ptr
to better specify the ownership semantics of pointer objects. Rather than mutate the source argument in a copy operation, std::unique_ptr
explicitly deletes the copy constructor and copy assignment operator, and instead uses a move constructor and move assignment operator. Subsequently, std::auto_ptr
was deprecated in C++11.
...
In this compliant solution, the copy operations for A
no longer mutate the source operand, ensuring that the vector contains equivalent copies of obj
. Instead, A
has been given move operations that perform the mutation when it is safe to do so.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <algorithm> #include <vector> class A { int m; public: A() : m(0) {} explicit A(int m) : m(m) {} A(const A &other) : m(other.m) {} A(A &&other) : m(other.m) { other.m = 0; } A& operator=(const A &other) { if (&other != this) { m = other.m; } return *this; } A& operator=(A &&other) { m = other.m; other.m = 0; return *this; } int get_m() const { return m; } }; void f() { std::vector<A> v{10}; A obj(12); std::fill(v.begin(), v.end(), obj); } |
Exceptions
OOP58-CPP-EX0: Reference counting, and implementations such as std::shared_ptr<>
constitute an exception to this rule. Any copy or assignment operation of a reference-counted object requires the reference count to be incremented. The semantics of reference counting are well-understood, and it can be argued that the reference count is not a salient part of the shared_pointer
object.
Risk Assessment
Copy operations that mutate the source operand or global state can lead to unexpected program behavior. In the case of using Using such a type in a Standard Template Library container or algorithm , this can also lead to undefined behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
OOP58-CPP | Low | Likely | Low | P9 | L2 |
Automated Detection
Tool | Version | Checker | Description |
---|
CodeSonar |
| LANG.FUNCS.COPINC | Copy Operation Parameter Is Not const | ||||||
Helix QAC |
| C++4075 | |||||||
Klocwork |
| CERT.OOP.COPY_MUTATES | |||||||
Parasoft C/C++test |
| CERT_CPP-OOP58-a | Copy operations must not mutate the source object | ||||||
Polyspace Bug Finder |
| CERT C++: OOP58-CPP | Checks for copy operation modifying source operand (rule partially covered) |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Bibliography
[ISO/IEC 14882-2014] | Subclause 12.8, "Copying and Moving Class Objects" Table 21, "CopyConstructible Requirements" Table 23, "CopyAssignable Requirements" |
[ISO/IEC 14882-2003] |
[Hinnant |
2005] | "Rvalue Reference Recommendations for Chapter 20" |
...
...