Let's look at some trivially move-constructible and (not trivially) copy-constructible (but still copy-constructible) user-defined (class) type A:
struct A
{
A() = default;
A(A const &) {}
A(A &&) = default;
};
Then moving of A (move-construction or move-assignment) literally perfroms the following: a source bitwise copied to a destination, despite of operation's name "moving". During trivial moving right hand side is (formally) not const, but triviality of the whole operation requires (actual) non-mutability of right hand side, isn't it? On my mind it means, that trivial copy-operation and trivial move-operation are exactly the same in their deep nature (in terms of memory, memory-layout, bits etc). Am I right?
If it is so, then I think, if I see trivially move-constructible, but not trivially copy-constructible type in user code, then I evidently see some antipattern. Am I right?
Is there an example of such artificial but usable type, which is not trivially copy-constructible/assignable, but trivially move-constructible/assignable?
Is there a use case where a type could have a trivial copy constructor without having a trivial move constructor? Sure.
For example, it could be useful to have a pointer wrapper type that will always be empty when moved from. There's no reason for the copy constructor to be non-trivial, but the move constructor would have to set the old value to NULL.
template<typename T>
class empty_on_move
{
T *ptr_;
public:
empty_on_move(const empty_on_move&) = default;
empty_on_move(empty_on_move &&other) : ptr_(other.ptr_) {other.ptr_ = nullptr;}
...
};
empty_on_move doesn't own the object, which is why it's OK to have multiple copies of it. It exists solely to make sure that when you move from it, the pointer is in a well-understood state. As such, is_trivially_copy_constructible<empty_on_move<T>> is true, while is_trivially_move_constructible<empty_on_move<T>> is false.
It would mainly be for use inside of other classes which want to give pointers that particular behavior. That way, you don't have to explicitly write code into their move constructors/assignments to NULL those fields out.
That being said, you're really asking the wrong question. Why? Because the answer doesn't matter.
The only time that the triviality of a copy/move constructor/assignment matters is when you need the type to be Trivially Copyable. It is that property which permits the use of memcpy and such things, not the trivially of the individual operations. The trivially copyable property requires that the copy/move constructor/assignment and destructors all are trivial (in C++14, the requirement is that they can be trivial or deleted, but at least one must be non-deleted).
If you're writing a wrapper around some type (or writing a sum/product type), and you want to expose the properties of that type, you only need concern yourself with exposing Trivial Copyability. That is, if T (or Ts...) is trivially copyable, then your type should be trivially copyable too.
But otherwise, you shouldn't feel the need to have a trivial copy constructor just because T does.
Related
A user-defined move constructor and move assignment operator could have been omitted in all examples explaining move semantics I have seen so far because compilers generated ones will do the job.
The answers I found in stackoverflow will not go beyond the "Rule of three" (Rule of five) - so if a class defines any of the following then it should probably explicitly define all five.
But for copy constructor and all other members the reason is obvious and an example could be written easily to show what issues can occur if user-defined copy constructor doesn't exist.
In wikipedia we can find:
A user-defined copy constructor is generally needed when an object owns pointers or non-shareable references, such as to a file.
So the question is if there are examples that will show if user-defined move constructor or move assignment operator are really needed or I can assume that in 99% of cases compiler generated ones will be enough.
I guess an example of necessary customized move operation is case of a class consisting of a container and some pointer(s) to its contents; say, a circular buffer with its head and tail.
But true, that's not that common to write.
Consider an relative offset pointer type like boost::interprocess::offset_ptr
The implementation is essentially a bit-like:
template<class T>
class offset_ptr
{
int64_t offset;
T* operator*()
{
return reinterpret_cast<T*>(this + this->offset);
}
};
You can see that the value of the pointer (i.e. what it points at) depends not only on the offset member but on the value of "this". A very clever trick (and not entirely standard's conforming). A member-wise move would be a disaster as the new value of this would make the pointer point to the wrong location.
According to cppreference, std::copyable is defined as follows:
template <class T>
concept copyable =
std::copy_constructible<T> &&
std::movable<T> && // <-- !!
std::assignable_from<T&, T&> &&
std::assignable_from<T&, const T&> &&
std::assignable_from<T&, const T>;
I'm wondering why a copyable object should be also movable. Just think about a global variable that is accessed by several functions. While it makes sense to copy that variable (for example to save its state before calling another function) it makes no sense, and actually would be very bad, to move it since other functions might not know that that variable is currently in an unspecified state. So why exactly does std::copyable subsume std::movable ?
This comes from two facts. Firstly, even if you don't define move constructor + move assignment you can still construct/assign object from r-value reference if you define copying functions. Just take a look at the example:
#include <utility>
struct foo {
foo() = default;
foo(const foo&) = default;
foo& operator=(const foo&) = default;
};
int main()
{
foo f;
foo b = std::move(f);
}
Secondly (and maybe more importantly), the copyable type can always be (or according to standard now must be) also movable in some way. If object is copyable then worst case scenario for move is just copying internal data.
Note that since I declared copy constructor the compiler DID NOT generate default move constructor.
While it makes sense to copy that variable (for example to save its state before calling another function) it makes no sense, and actually would be very bad, to move it since other functions might not know that that variable is currently in an unspecified state.
There's a strong, unstated presumption here of what moving actually means that is probably the source of confusion. Consider the type:
class Person {
std::string name;
public:
Person(std::string);
Person(Person const& rhs) : name(rhs.name) { }
Person& operator=(Person const& rhs) { name = rhs.name; return *this; }
};
What does moving a Person do? Well, an rvalue of type Person can bind to Person const&... and that'd be the only candidate... so moving would invoke the copy constructor. Moving does a copy! This isn't a rare occurrence either - moving doesn't have to be destructive or more efficient than copying, it just can be.
Broadly speaking, there are four sane categories of types:
Types for which move and copy do the same thing (e.g. int)
Types for which move can be an optimization of copy that consumes resources (e.g. string or vector<int>)
Types which can be moved but not copied (e.g. unique_ptr<int>)
Types which can be neither moved nor copied (e.g. mutex)
There are a lot of types that fall into group 1 there. And the kind of variable mentioned in OP should also fall into group 1.
Notably missing from this list is a type that is copyable but not movable, since that makes very little sense from an operational stand-point. If you can copy the type, and you don't want destructive behavior on moving, just make moving also copy the type.
As such, you can view these groups as a kind of hierarchy. (3) expands on (4), and (1) and (2) expand on (3) - you can't really differentiate (1) from (2) syntactically. Hence, copyable subsumes movable.
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.
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;
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.