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.
If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.
Unlike the default constructor, the body of the copy constructor created by the compiler is not empty, it copies all data members of the passed object to the object which is being created.
A move constructor is executed only when you construct an object. A move assignment operator is executed on a previously constructed object. It is exactly the same scenario as in the copy case.
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()); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With