C.copy
C.copy: Copy and move
Concrete types should generally be copyable, but interfaces in a class hierarchy should not. Resource handles might or might not be copyable. Types can be defined to move for logical as well as performance reasons.
C.60: Make copy assignment non-virtual
, take the parameter by const&
, and return by non-const&
Reason
It is simple and efficient. If you want to optimize for rvalues, provide an overload that takes a &&
(see F.18).
Example
class Foo {
public:
Foo& operator=(const Foo& x)
{
// GOOD: no need to check for self-assignment (other than performance)
auto tmp = x;
swap(tmp); // see C.83
return *this;
}
// ...
};
Foo a;
Foo b;
Foo f();
a = b; // assign lvalue: copy
a = f(); // assign rvalue: potentially move
Note
The swap
implementation technique offers the strong guarantee.
Example
But what if you can get significantly better performance by not making a temporary copy? Consider a simple Vector
intended for a domain where assignment of large, equal-sized Vector
s is common. In this case, the copy of elements implied by the swap
implementation technique could cause an order of magnitude increase in cost:
template<typename T>
class Vector {
public:
Vector& operator=(const Vector&);
// ...
private:
T* elem;
int sz;
};
Vector& Vector::operator=(const Vector& a)
{
if (a.sz > sz) {
// ... use the swap technique, it can't be bettered ...
return *this;
}
// ... copy sz elements from *a.elem to elem ...
if (a.sz < sz) {
// ... destroy the surplus elements in *this and adjust size ...
}
return *this;
}
By writing directly to the target elements, we will get only the basic guarantee rather than the strong guarantee offered by the swap
technique. Beware of self-assignment.
Alternatives: If you think you need a virtual
assignment operator, and understand why that's deeply problematic, don't call it operator=
. Make it a named function like virtual void assign(const Foo&)
.
See copy constructor vs. clone()
.
Enforcement
- (Simple) An assignment operator should not be virtual. Here be dragons!
- (Simple) An assignment operator should return
T&
to enable chaining, not alternatives likeconst T&
which interfere with composability and putting objects in containers. - (Moderate) An assignment operator should (implicitly or explicitly) invoke all base and member assignment operators. Look at the destructor to determine if the type has pointer semantics or value semantics.
C.61: A copy operation should copy
Reason
That is the generally assumed semantics. After x = y
, we should have x == y
.
After a copy x
and y
can be independent objects (value semantics, the way non-pointer built-in types and the standard-library types work) or refer to a shared object (pointer semantics, the way pointers work).
Example
class X { // OK: value semantics
public:
X();
X(const X&); // copy X
void modify(); // change the value of X
// ...
~X() { delete[] p; }
private:
T* p;
int sz;
};
bool operator==(const X& a, const X& b)
{
return a.sz == b.sz && equal(a.p, a.p + a.sz, b.p, b.p + b.sz);
}
X::X(const X& a)
:p{new T[a.sz]}, sz{a.sz}
{
copy(a.p, a.p + sz, p);
}
X x;
X y = x;
if (x != y) throw Bad{};
x.modify();
if (x == y) throw Bad{}; // assume value semantics
Example
class X2 { // OK: pointer semantics
public:
X2();
X2(const X2&) = default; // shallow copy
~X2() = default;
void modify(); // change the pointed-to value
// ...
private:
T* p;
int sz;
};
bool operator==(const X2& a, const X2& b)
{
return a.sz == b.sz && a.p == b.p;
}
X2 x;
X2 y = x;
if (x != y) throw Bad{};
x.modify();
if (x != y) throw Bad{}; // assume pointer semantics
Note
Prefer value semantics unless you are building a "smart pointer". Value semantics is the simplest to reason about and what the standard-library facilities expect.
Enforcement
(Not enforceable)
C.62: Make copy assignment safe for self-assignment
Reason
If x = x
changes the value of x
, people will be surprised and bad errors will occur (often including leaks).
Example
The standard-library containers handle self-assignment elegantly and efficiently:
std::vector<int> v = {3, 1, 4, 1, 5, 9};
v = v;
// the value of v is still {3, 1, 4, 1, 5, 9}
Note
The default assignment generated from members that handle self-assignment correctly handles self-assignment.
struct Bar {
vector<pair<int, int>> v;
map<string, int> m;
string s;
};
Bar b;
// ...
b = b; // correct and efficient
Note
You can handle self-assignment by explicitly testing for self-assignment, but often it is faster and more elegant to cope without such a test (e.g., using swap
).
class Foo {
string s;
int i;
public:
Foo& operator=(const Foo& a);
// ...
};
Foo& Foo::operator=(const Foo& a) // OK, but there is a cost
{
if (this == &a) return *this;
s = a.s;
i = a.i;
return *this;
}
This is obviously safe and apparently efficient. However, what if we do one self-assignment per million assignments? That's about a million redundant tests (but since the answer is essentially always the same, the computer's branch predictor will guess right essentially every time). Consider:
Foo& Foo::operator=(const Foo& a) // simpler, and probably much better
{
s = a.s;
i = a.i;
return *this;
}
std::string
is safe for self-assignment and so are int
. All the cost is carried by the (rare) case of self-assignment.
Enforcement
(Simple) Assignment operators should not contain the pattern if (this == &a) return *this;
???
C.63: Make move assignment non-virtual
, take the parameter by &&
, and return by non-const&
Reason
It is simple and efficient.
See: The rule for copy-assignment.
Enforcement
Equivalent to what is done for copy-assignment.
- (Simple) An assignment operator should not be virtual. Here be dragons!
- (Simple) An assignment operator should return
T&
to enable chaining, not alternatives likeconst T&
which interfere with composability and putting objects in containers. - (Moderate) A move assignment operator should (implicitly or explicitly) invoke all base and member move assignment operators.
C.64: A move operation should move and leave its source in a valid state
Reason
That is the generally assumed semantics.
After y = std::move(x)
the value of y
should be the value x
had and x
should be in a valid state.
Example
class X { // OK: value semantics
public:
X();
X(X&& a) noexcept; // move X
X& operator=(X&& a) noexcept; // move-assign X
void modify(); // change the value of X
// ...
~X() { delete[] p; }
private:
T* p;
int sz;
};
X::X(X&& a) noexcept
:p{a.p}, sz{a.sz} // steal representation
{
a.p = nullptr; // set to "empty"
a.sz = 0;
}
void use()
{
X x{};
// ...
X y = std::move(x);
x = X{}; // OK
} // OK: x can be destroyed
Note
Ideally, that moved-from should be the default value of the type. Ensure that unless there is an exceptionally good reason not to. However, not all types have a default value and for some types establishing the default value can be expensive. The standard requires only that the moved-from object can be destroyed. Often, we can easily and cheaply do better: The standard library assumes that it is possible to assign to a moved-from object. Always leave the moved-from object in some (necessarily specified) valid state.
Note
Unless there is an exceptionally strong reason not to, make x = std::move(y); y = z;
work with the conventional semantics.
Enforcement
(Not enforceable) Look for assignments to members in the move operation. If there is a default constructor, compare those assignments to the initializations in the default constructor.
C.65: Make move assignment safe for self-assignment
Reason
If x = x
changes the value of x
, people will be surprised and bad errors can occur. However, people don't usually directly write a self-assignment that turn into a move, but it can occur. However, std::swap
is implemented using move operations so if you accidentally do swap(a, b)
where a
and b
refer to the same object, failing to handle self-move could be a serious and subtle error.
Example
class Foo {
string s;
int i;
public:
Foo& operator=(Foo&& a) noexcept;
// ...
};
Foo& Foo::operator=(Foo&& a) noexcept // OK, but there is a cost
{
if (this == &a) return *this; // this line is redundant
s = std::move(a.s);
i = a.i;
return *this;
}
The one-in-a-million argument against if (this == &a) return *this;
tests from the discussion of self-assignment is even more relevant for self-move.
Note
There is no known general way of avoiding an if (this == &a) return *this;
test for a move assignment and still get a correct answer (i.e., after x = x
the value of x
is unchanged).
Note
The ISO standard guarantees only a "valid but unspecified" state for the standard-library containers. Apparently this has not been a problem in about 10 years of experimental and production use. Please contact the editors if you find a counter example. The rule here is more caution and insists on complete safety.
Example
Here is a way to move a pointer without a test (imagine it as code in the implementation a move assignment):
// move from other.ptr to this->ptr
T* temp = other.ptr;
other.ptr = nullptr;
delete ptr; // in self-move, this->ptr is also null; delete is a no-op
ptr = temp; // in self-move, the original ptr is restored
Enforcement
- (Moderate) In the case of self-assignment, a move assignment operator should not leave the object holding pointer members that have been
delete
d or set tonullptr
. - (Not enforceable) Look at the use of standard-library container types (incl.
string
) and consider them safe for ordinary (not life-critical) uses.
C.66: Make move operations noexcept
Reason
A throwing move violates most people's reasonable assumptions. A non-throwing move will be used more efficiently by standard-library and language facilities.
Example
template<typename T>
class Vector {
public:
Vector(Vector&& a) noexcept :elem{a.elem}, sz{a.sz} { a.elem = nullptr; a.sz = 0; }
Vector& operator=(Vector&& a) noexcept {
delete elem;
elem = a.elem; a.elem = nullptr;
sz = a.sz; a.sz = 0;
return *this;
}
// ...
private:
T* elem;
int sz;
};
These operations do not throw.
Example, bad
template<typename T>
class Vector2 {
public:
Vector2(Vector2&& a) noexcept { *this = a; } // just use the copy
Vector2& operator=(Vector2&& a) noexcept { *this = a; } // just use the copy
// ...
private:
T* elem;
int sz;
};
This Vector2
is not just inefficient, but since a vector copy requires allocation, it can throw.
Enforcement
(Simple) A move operation should be marked noexcept
.
C.67: A polymorphic class should suppress public copy/move
Reason
A polymorphic class is a class that defines or inherits at least one virtual function. It is likely that it will be used as a base class for other derived classes with polymorphic behavior. If it is accidentally passed by value, with the implicitly generated copy constructor and assignment, we risk slicing: only the base portion of a derived object will be copied, and the polymorphic behavior will be corrupted.
If the class has no data, =delete
the copy/move functions. Otherwise, make them protected.
Example, bad
class B { // BAD: polymorphic base class doesn't suppress copying
public:
virtual char m() { return 'B'; }
// ... nothing about copy operations, so uses default ...
};
class D : public B {
public:
char m() override { return 'D'; }
// ...
};
void f(B& b)
{
auto b2 = b; // oops, slices the object; b2.m() will return 'B'
}
D d;
f(d);
Example
class B { // GOOD: polymorphic class suppresses copying
public:
B() = default;
B(const B&) = delete;
B& operator=(const B&) = delete;
virtual char m() { return 'B'; }
// ...
};
class D : public B {
public:
char m() override { return 'D'; }
// ...
};
void f(B& b)
{
auto b2 = b; // ok, compiler will detect inadvertent copying, and protest
}
D d;
f(d);
Note
If you need to create deep copies of polymorphic objects, use clone()
functions: see C.130.
Exception
Classes that represent exception objects need both to be polymorphic and copy-constructible.
Enforcement
- Flag a polymorphic class with a public copy operation.
- Flag an assignment of polymorphic class objects.