Rule of 3 Default Member Deprecation in C++11 - c++

According to the below widely-known table, automatic compiler generation of default copy constructor and copy assignment is deprecated in C++11 when one or more of the copy assignment, copy constructor, and destructor is/are supplied by the user (the red cells indicate deprecation). This makes perfect sense in light of the "Rule of 3". However, the table shows that generation of the default destructor is not deprecated in the case of user-supplied copy constructor/assignment.
What is the rationale behind this design decision?

Why should it be deprecated? It's perfectly possible for an object to require special copying properties, but its for destruction to be fully determined by its sub-object destructors. Consider a simple cloning pointer:
template <class T>
class cloning_ptr
{
std::unique_ptr<T> p;
public:
cloning_ptr(const cloning_ptr &src) : p(std::make_unique<T>(*src.p) {}
cloning_ptr(cloning_ptr &&) = default;
cloning_ptr& operator= (cloning_ptr rhs)
{ swap(p, rhs.p); return *this; }
};
There's zero reason to provide a destructor that does anything different from the defaulted one.
The other way around is different: if you need to do special things in a dtor, that probably means there is some non-standard ownership modeled in the class. Non-standard ownership will most likely need handling in copy operations, too.

Related

What is the Rule of Four (and a half)?

For properly handling object copying, the rule of thumb is the Rule of Three. With C++11, move semantics are a thing, so instead it's the Rule of Five. However, in discussions around here and on the internet, I've also seen references to the Rule of Four (and a half), which is a combination of the Rule of Five and the copy-and-swap idiom.
So what exactly is the Rule of Four (and a half)? Which functions need to be implemented, and what should each function's body look like? Which function is the half? Are there any disadvantages or warnings for this approach, compared to the Rule of Five?
Here's a reference implementation that resembles my current code. If this is incorrect, what would a correct implementation look like?
#include <utility>
// "Handle management" functions. These would be defined in an external library.
typedef int* handle;
#define NO_HANDLE nullptr
extern handle get_handle(int value);
extern handle copy_handle(handle h);
extern void free_handle(handle h);
// This class automatically obtains a handle, and then frees it when the object
// leaves scope.
class AutoHandle {
public:
//We must have a default constructor so we can swap during copy construction.
//It need not be useful, but it should be swappable and deconstructable.
//It can be private, if it's not truly a valid state for the object.
AutoHandle() : resource(NO_HANDLE) {}
//Normal constructor, acquire resource
AutoHandle(int value) : resource(get_handle(value)) {}
//Copy constructor
AutoHandle(AutoHandle const& other) {
resource = copy_handle(other.resource);
}
//Move constructor
//Delegates to default constructor to put us in safe state.
AutoHandle(AutoHandle&& other) : AutoHandle() {
swap(other);
}
//Assignment
AutoHandle& operator=(AutoHandle other) {
swap(other);
return *this;
}
//Destructor
~AutoHandle() {
//Free the resource here.
//We must handle the default state that can appear from the copy ctor.
if (resource != NO_HANDLE)
free_handle(resource);
}
//Swap
void swap(AutoHandle& other) {
using std::swap;
//Swap the resource between instances here.
swap(resource, other.resource);
}
//Swap for ADL
friend void swap(AutoHandle& left, AutoHandle& right) {
left.swap(right);
}
private:
handle resource;
};
So what exactly is the Rule of Four (and a half)?
“The Rule of The Big Four (and a half)" states that if you implement one of
The copy constructor
The assignment operator
The move constructor
The destructor
The swap function
then you must have a policy about the others.
Which functions need to implemented, and what should each function's body look like?
default constructor (which could be private)
copy constructor (deep copy of your resource. Here you have real code to handle your resource)
move constructor (using default constructor and swap) :
S(S&& s) : S{} { swap(*this, s); }
assignment operator (using constructor and swap)
S& operator=(S s)
{
swap(*this, s);
return *this;
}
destructor (release your resources)
friend swap (doesn't have default implementation :/ you should probably want to swap each member). This one is important contrary to the swap member method: std::swap uses move (or copy) constructor, which would lead to infinite recursion.
Which function is the half?
From previous article:
"To implement the Copy-Swap idiom your resource management class must also implement a swap() function to perform a member-by-member swap (there’s your “…(and a half)”)"
so the swap method.
Are there any disadvantages or warnings for this approach, compared to the Rule of Five?
The warning I already wrote is about to write the correct swap to avoid the infinite recursion.
Are there any disadvantages or warnings for this approach, compared to the Rule of Five?
Although it can save code duplication, using copy-and-swap simply results in worse classes, to be blunt. You are hurting your class' performance, including move assignment (if you use the unified assignment operator, which I'm also not a fan of), which should be very fast. In exchange, you get the strong exception guarantee, which seems nice at first. The thing is, that you can get the strong exception guarantee from any class with a simple generic function:
template <class T>
void copy_and_swap(T& target, T source) {
using std::swap;
swap(target, std::move(source));
}
And that's it. So people who need strong exception safety can get it anyway. And frankly, strong exception safety is quite a niche anyhow.
The real way to save code duplication is through the Rule of Zero: choose member variables so that you don't need to write any of the special functions. In real life, I'd say that 90+ % of the time I see special member functions, they could have easily been avoided. Even if your class does indeed have some kind of special logic required for a special member function, you are usually better off pushing it down into a member. Your logger class may need to flush a buffer in its destructor, but that's not a reason to write a destructor: write a small buffer class that handles the flushing and have that as a member of your logger. Loggers potentially have all kinds of other resources that can get handled automatically and you want to let the compiler automatically generate copy/move/destruct code.
The thing about C++ is that automatic generation of special functions is all or nothing, per function. That is the copy constructor (e.g.) either gets generated automatically, taking into account all members, or you have to write (and worse, maintain) it all by hand. So it strongly pushes you to an approach of pushing things downwards.
In cases where you are writing a class to manage a resource and need to deal with this, it should typically be: a) relatively small, and b) relatively generic/reusable. The former means that a bit of duplicated code isn't a big deal, and the latter means that you probably don't want to leave performance on the table.
In sum I strongly discourage using copy and swap, and using unified assignment operators. Try to follow the Rule of Zero, if you can't, follow the Rule of Five. Write swap only if you can make it faster than the generic swap (which does 3 moves), but usually you needn't bother.
In simple terms, just remember this.
Rule of 0:
Classes have neither custom destructors, copy/move constructors or copy/move assignment operators.
Rule of 3:
If you implement a custom version of any of these, you implement all of them.
Destructor, Copy constructor, copy assignment
Rule of 5:
If you implement a custom move constructor or the move assignment operator, you need to define all 5 of them. Needed for move semantics.
Destructor, Copy constructor, copy assignment, move constructor, move assignment
Rule of four and a half:
Same as Rule of 5 but with copy and swap idiom. With the inclusion of the swap method, the copy assignment and move assignment merge into one assignment operator.
Destructor, Copy constructor, move constructor, assignment, swap (the half part)
Destructor: ~Class();
Copy constructor: Class(Class &);
Move constructor: Class(Class &&);
Assignment: Class & operator = (Class);
Swap: void swap(Class &);
There are no warnings, the advantage is that it is faster in assignment as a pass by value copy is actually more efficient than creating a temporary object in the body of the method.
And now that we have that temporary object, we simply perform a swap on the temporary object. It's automatically destroyed when it goes out of scope and we now have the value from the right-hand side of the operator in our object.
References:
https://www.linkedin.com/learning/c-plus-plus-advanced-topics/rule-of-five?u=67551194
https://en.cppreference.com/w/cpp/language/rule_of_three

How does the move constructor look like if I have a vector (or anything like it) member variable?

The title pretty much sums up my question. In more detail: I know that when I declare a move constructor and a move assignment operator in C++11 I have to "make the other objects variables zero". But how does that work, when my variable is not an array or a simple int or double value, but its a more "complex" type?
In this example I have a Shoplist class with a vector member variable. Do I have to invoke the destructor of the vector class in the move assignment operator and constructor? Or what?
class Shoplist {
public:
Shoplist() :slist(0) {};
Shoplist(const Shoplist& other) :slist(other.slist) {};
Shoplist(Shoplist&& other) :slist(0) {
slist = other.slist;
other.slist.~vector();
}
Shoplist& operator=(const Shoplist& other);
Shoplist& operator=(Shoplist&& other);
~Shoplist() {};
private:
vector<Item> slist;
};
Shoplist& Shoplist::operator=(const Shoplist& other)
{
slist = other.slist;
return *this;
}
Shoplist& Shoplist::operator=(Shoplist&& other)
{
slist = other.slist;
other.slist.~vector();
return *this;
}
Whatever a std::vector needs to do in order to move correctly, will be handled by its own move constructor.
So, assuming you want to move the member, just use that directly:
Shoplist(Shoplist&& other)
: slist(std::move(other.slist))
{}
and
Shoplist& Shoplist::operator=(Shoplist&& other)
{
slist = std::move(other.slist);
return *this;
}
In this case, you could as AndyG points out, just use = default to have the compiler generate exactly the same move ctor and move assignment operator for you.
Note that explicitly destroying the original as you did is definitely absolutely wrong. The other member will be destroyed again when other goes out of scope.
Edit: I did say assuming you want to move the member, because in some cases you might not.
Generally you want to move data members like this if they're logically part of the class, and much cheaper to move than copy. While std::vector is definitely cheaper to move than to copy, if it holds some transient cache or temporary value that isn't logically part of the object's identity or value, you might reasonably choose to discard it.
Implementing copy/move/destructor operations doesn't make sense unless your class is managing a resource. By managing a resource I mean be directly responsible for it's lifetime: explicit creation and destruction. The rule of 0 and The rule of 3/5 stem from this simple ideea.
You might say that your class is managing the slist, but that would be wrong in this context: the std::vector class is directly (and correctly) managing the resources associated with it. If you let our class have implicit cpy/mv ctos/assignment and dtors, they will correctly invoke the corresponding std::vector operations. So you absolutely don't need to explicitly define them. In your case the rule of 0 applies.
I know that when I declare a move constructor and a move assignment
operator in C++11 I have to "make the other objects variables zero"
Well no, not really. The ideea is that when you move from an object (read: move it's resource from an object) then you have to make sure that your object it's left aware that the resource it had is no more under it's ownership (so that, for instance, it doesn't try to release it in it's destructor). In the case of std::vector, it's move ctor would set the pointer it has to the internal buffer to nullptr.
I know that when I declare a move constructor and a move assignment operator in C++11 I have to "make the other objects variables zero"
This is not quite correct. What you must do, is maintain validity of the moved from object. This means that you must satisfy the class invariant.
If you have specified a special invariant for a particular class, that requires you to set member variables to zero, then perhaps such class might have to do so. But this is not a requirement for move in general.
Do I have to invoke the destructor of the vector class in the move assignment operator and constructor?
Definitely not. The destructors of the members will be called when the moved from object is destroyed.
What you would typically do, is move construct/assign each member in the move constructor/assignment operator of the containing object. This is what the implicitly generated special member functions do. Of course, this might not satisfy the class invariant for all classes, and if it doesn't, then you may need to write your own versions of them.
The compiler will implicitly generate the special member functions for you, if you don't try to declare them yourself. Here is a minimal, but correct version of your class:
class Shoplist {
vector<Item> slist;
};
This class is default constructible, movable and copyable.
The move constructor should move member-wise:
Shoplist(Shoplist&& other)
: slist(std::move(other.slist))
{}
Note, that the compiler generates move constructors for you (when possible) by member-wise move, as you would do by hand above.
Move constructors are allowed (but not required) "steal" the contents of the moved-from object. This does not mean that they must "make the other objects variables zero". Moving a primitive type, for instance, is equivalent to copying it. What it does mean is that a move constructor can transfer ownership of data in the heap or free store. In this case, the moved-from object must be modified so that when it is destroyed (which should not happen in the move-constructor), the data it previously owned (before it was transferred) will not be freed.
Vector provides its own move constructor. So all you need to do in order to write a correct move constructor for an object containing a vector is to ensure the correct vector constructor is invoked. This is done by explicitly passing an r-value reference to the sub-object constructor, using std::move:
Shoplist(Shoplist&& other) :slist(std::move(other.slist)) {
//... Constructor body
... But in fact you probably don't need to do this in general. Your copy and move constructors will be correctly auto-generated if you don't declare them and don't declare a destructor. (Following this practice is called the "rule of 0".)
Alternatively, you can force the compiler to auto-generate the move constructor:
Shoplist(Shoplist&& other) = default;

Why not synthesize move operations for a class that defines the destructor as =default?

The rule that the compiler shouldn't synthesize move operations for a class that declares a destructor or a copy operation (i.e. copy constructor/assignment) makes sense. Afterall, by declaring these operations, the class admits that it needs to do some custom bookkeeping.
However, this reasoning does not apply when the class defines the destructor or a copy operation as =default? Shouldn't then this case be an exception from the rule?
EDIT: One reason I might want to define the destructor as =default, but not the other special operations, is when I need a virtual destructor for the base class, so I am forced to define one to make it virtual.
N3174 seems to have been the proposal that introduced the rule user-declared dtor/copy op => no implicit move. In this paper, Bjarne Stroustrup shows concern for certain class invariants, as in:
class Vec {
vector<int> v;
int my_int; // the index of my favorite int stored in v
// implicit/generated copy, move, and destructor
// ...
};
Vec has what I call an implicit invariant: There is a relation
between two objects (here, the members) that is nowhere stated in
declaratively in the code.
Note that this invariant is broken by move operations, which is especially evil if they're implicitly generated, and change the meaning of code that was formerly well-behaved (in C++03 where rvalues are copied).
Because of these invariants, Stroustrup proposed:
Move and copy are generated by default (if an only if their elements move or copy as currently specified in the FCD [dyp: FCD =
final committee draft])
If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, no copy or move is
generated by default.
(Later, he suggests to only deprecate implicit generation of copy in 2., not remove it entirely in C++11)
The best explanation I can find in N3174 why defaulted user-declared operations are included in the second bullet point is the relation between invariants and special member functions:
I think that the most worrying cases are equivalent to Vec. For such
classes there is an implicit invariant but no “indication” to help the
compiler [dyp: discover the existence of an invariant] in the form of a user-specified copy constructor. This kind
of example occurs (occurred) when a programmer decided that the
default copy operations were correct and then (correctly) decided not
to mention them because the default copy operations are superior to
user-defined ones (e.g. because of ABI issues). In C++0x, a programmer
can be explicit by defaulting copy, but that could be considered
undesirably verbose.
So by writing Vec as follows:
class Vec {
vector<int> v;
int my_int; // the index of my favorite int stored in v
public:
Vec() = default;
Vec(Vec const&) = default;
Vec& operator=(Vec const&) = default;
~Vec() = default;
};
we can state that the default copy operations are safe, while the default move operations are not safe. If the user had to explicitly define the move operations as deleted, this would "leave[..] a hole in the expressions" (see N3053: trying to copy from, or return (non-const) rvalues will attempt to use the deleted move operations.
It is not entirely obvious why the dtor should be included in that list, but I think it belongs to the group of 3/5 special member functions where class invariants often occur.
I think part of the reason is that if you've written a defaulted destructor then you are already writing (at least) C++11 and so there is no reason you should not be able to also write a defaulted move constructor and move assignment operator.
The rule protects against classes written pre-C++11 from silently being given move semantics that might break the class invariants. If the class is not pre-C++11 then you can decide explicitly whether you want move semantics and enable them with =default.

Default move constructor vs. Default copy constructor vs. Default assignment operator

Why does C++ compiler have more restriction on automatically generated move constructors than on automatically generated copy constructor or assignment operator ?
Automatically generated move constructors are generated only if user has defined nothing (i.e: constructor, copy, assignment, destructor..)
Copy constructor or assignment operator are generated only if user has not defined respectively copy constructor or assignment operator.
I wonder why the difference.
I believe backwards compatibility plays a big part here. If the user defines any of the "Rule of three" functions (copy ctor, copy assignment op, dtor), it can be assumed the class does some internal resource management. Implicitly defining a move constructor could suddenly make the class invalid when compiled under C++11.
Consider this example:
class Res
{
int *data;
public:
Res() : data(new int) {}
Res(const Res &arg) : data(new int(*arg.data)) {}
~Res() { delete data; }
};
Now if a default move constructor was generated for this class, its invocation would lead to a double deletion of data.
As for the move assignment operator preventing default move constructor definitions: if the move assignment operator does something other than default one, it would most likely be wrong to use the default move constructor. That's just the "Rule of three"/"Rule of five" in effect.
As far as I know, this is because of downward compatibility. Consider classes written in C++ (before C++11) and what would happen if C++11 would start to automatically generate move-ctors in parallel to existing copy-ctors or generally any other ctor. It would easily break existing code, by-passing the copy-ctor the author of that class wrote. Hence, the rules for generating a move-ctor where crafted to only apply to "safe" cases.
Here's the article from Dave Abrahams about why implicit move must go, which eventually led to the current rules of C++11.
And this is an example how it would fail:
// NOTE: This example assumes an implicitly generated move-ctor
class X
{
private:
std::vector<int> v;
public:
// invariant: v.size() == 5
X() : v(5) {}
~X()
{
std::cout << v[0] << std::endl;
}
};
int main()
{
std::vector<X> y;
// and here is where it would fail:
// X() is an rvalue: copied in C++03, moved in C++0x
// the classes' invariant breaks and the dtor will illegally access v[0].
y.push_back(X());
}
When C++ was created, it was decided that default constructor, copy-constructor, assignment-operator and destructor would be generated automatically (unless provided). Why ? Because C++ compilers should be able to compile (most) C code with identical semantics, and that's how struct work in C.
However, it was later noticed that whenever a user writes a custom destructor, she probably needs to write a custom copy-constructor/assignment-operator too; this is known as the Rule of Big Three. With hindsight, we can see that it could have been specified that the generated copy-constructor/assignment-operator/destructor would have only been generated if none of the 3 were user-provided, and it would have helped catch a lot of bugs... and still retain backward compatibility with C.
Therefore, as C++11 came around, it was decided that this time things would be done right: the new move-constructor and move-assignment-operator would only be generated automatically if it was clear that the user was not doing anything "special" with the class. Anything "special" being defined as redefining move/copy/destruction behavior.
To help with the case were people would be doing something special but still wanted "automatically generated" special methods, the = default sugar-coating was added as well.
Unfortunately, for backward compatibility reasons, the C++ committee could not go back in time and change the rules of automatic generation for copy; I wish they had deprecated it to pave the way for the next version of the Standard, but I doubt they will. it is however deprecated (see §12.8/7 for the copy constructor for example, courtesy of #Nevin).

std::unique_ptr compiler error: Members of a derived class cannot access private members of a base class

I get Compiler Error C2248 when i try to compile the following code:
#include <list>
#include <memory>
using namespace std;
class data
{
public:
static data parse()
{
data d;
data::parse(d);
return d;
}
list<std::unique_ptr<data>> l;
private:
static void parse(data& node)
{ }
};
int main()
{
return 0;
}
Why? How can i fix this?
Note: I have no problem using std::shared_ptr instead of std::unique_ptr.
You need to provide move operations for your type:
data(data&& other)
: l(std::move(other.l))
{
}
data& operator=(data&& other)
{
l = std::move(other.l);
return *this;
}
And, since you'll have added a user-declared constructor, you'll also need a user-declared default constructor:
data() { }
My understanding is that your code is correct as-is, per the final C++11 language standard. Visual C++ does not fully implement the final specification for when move operations are implicitly generated (as of the Visual C++ 2012 RC). The specification for when implicit move operations are generated changed several times very late in the standardization process.
If you have a class type C that has any data member that is movable but noncopyable, Visual C++ will not generate an implicit move constructor or move assignment operator, and the implicit copy constructor and copy assignment operator are both suppressed by the presence of the move-only data member. In other words, if you want ot aggregate move-only types, you must provide the move operations for the aggregating class yourself.
(At least, this is my understanding from experimentation with the compiler.)
First things first, VC++ doesn't automatically generate a move ctor and move assignment operator yet, which means you need to define them yourself.
Next, when you return local variables, the compiler first tries to move them before actually going the usual route of copying them. However, to do that, it needs a move ctor. Since it doesn't have that, it tries the usual copy and through the generated copy ctor automatically invokes the copy constructor of std::list which in turn tries to invoke the copy ctor of its element type, which is private in std::unique_ptrs case.
You need to either define an appropriate move ctor or a copy ctor that doesn't invoke std::unique_ptr's copy ctor (i.e., make a deep copy of the content).
Short answer: (C++11 specific) Items in a list must be copyable or movveable. A unique_ptr is not copyable by-design, but it is moveable, so long as the controlled type is also moveable.
Your type, data is not moveable because you have not implemented move semantics and the compiler did not do it for you.
Implement move semantics, and you can use unique_ptr in a list:
data(ddata&&) {};
According to thhe Standard, a move constructor would be generated for your class by the compiler. However, VS10 does not support this -- this might be the problem your'e running in to.
For further reference, see my post on CR: Canonical Implementation of Move Semantics