In C++11, one can explicitly default a special member function, if its implicit generation was automatically prevented.
However, explicitly defaulting a special member function only undoes the implicit deletion caused by manually declaring some of the other special member functions (copy operations, destructor, etc.), it does not force the compiler to generate the function and the code is considered to be well formed even if the function can't in fact be generated.
Consider the following scenario:
struct A
{
A () = default;
A (const A&) = default;
A (A&&) = delete; // Move constructor is deleted here
};
struct B
{
B () = default;
B (const B&) = default;
B (B&&) = default; // Move constructor is defaulted here
A a;
};
The move constructor in B will not be generated by the compiler, because doing so would cause a compilation error (A's move constructor is deleted). Without explicitly deleting A's constructor, B's move constructor would be generated as expected (copying A, rather than moving it).
Attempting to move such an object will silently use the copy constructor instead:
B b;
B b2 (std::move(b)); // Will call B's copy constructor
Is there a way to force the compiler into either generating the function or issue a compilation error if it can't? Without this guarantee, it's difficult to rely on defaulted move constructors, if a single deleted constructor can disable move for entire object hierarchies.
There is a way to detect types like A. But only if the type explicitly deletes the move constructor. If the move constructor is implicitly generated as deleted, then it will not participate in overload resolution. This is why B is movable even though A is not. B defaults the move constructor, which means it gets implicitly deleted, so copying happens.
B is therefore move constructible. However, A is not. So it's a simple matter of this:
struct B
{
static_assert(is_move_constructible<A>::value, "Oops...");
B () = default;
B (const B&) = default;
B (B&&) = default; // Move constructor is defaulted here
A a;
};
Now, there is no general way to cause any type which contains copy-only types to do what you want. That is, you have to static assert on each type individually; you can't put some syntax in the default move constructor to make attempts to move B fail.
The reason for that has to do in part with backwards compatibility. Think about all the pre-C++11 code that declared user-defined copy constructors. By the rules of move constructor generation in C++11, all of them would have deleted move constructors. Which means that any code of the form T t = FuncReturningTByValue(); would fail, even though it worked just fine in C++98/03 by calling the copy constructor. So the move-by-copy issue worked around this by making these copy instead of moving if the move constructor could not be generated.
But since = default means "do what you would normally do", it also includes this special overload resolution behavior that ignores the implicitly deleted move constructor.
Related
I am struggling to understand implicit move operations when a class has a member whose move operations were not defined:
int main() {
struct A // no move: move = copy
{
A() = default;
A(const A&) {
cout << "A'copy-ctor\n";
};
A& operator=(const A&) {
cout << "A'copy-assign\n";
return *this;
}
};
struct B
{
B() = default;
A a; // does this make B non-moveable?
unique_ptr<int> upi;
// B(B&&) noexcept = default;
// B& operator=(B&&)noexcept = default;
};
A a;
A a2 = std::move(a); // ok use copy ctor instead of move one
a2 = std::move(a); // ok use copy assignment instead of move one
B b;
B b2 = std::move(b); // why this works?
b = std::move(b2); // and this works?
// b = b2; // error: copy deleted because of non-copyable member upi
cout << "\nDone!\n";
}
So what I see is A is a non-moveable class because of the definition of its copy control operations so it can only be copied and any attempt to move an object of this class, the corresponding copy operation is used instead.
Until here it is OK if i am correct. But B has a non-copy-able object upi which is a unique_ptr thus the copy operations are defined as deleted functions so we cannot copy objects of this class. But this class has a non-move-able object a thus i think that this class (B) is neither copy-able nor move-able. But why the initialization of b2 and the assignment of b works fine? What happens exactly?
B b2 = std::move(b); // ok?!
Why the line above invokes the copy constructor of class A and does it invoke move constructor of B?
Things get more worse for me: if I uncomment the lines of move operations in B, the initialization above will not compile complaining about referencing a deleted funtion, the same thing for the assignment!
Can anyone help me what happens exactly? I have googled and read in cppreference and many websites before posting the question here.
The output:
A'copy-ctor
A'copy-assign
A'copy-ctor
A'copy-assign
Done!
Keep in mind what it means to "move" data in C++ (assuming we follow the usual conventions). If you move object x to object y, then y receives all the data that was in x and x is... well, we don't care what x is as long as it is still valid for destruction. Often we think of x as losing all of its data, but that is not required. All that is required is that x is valid. If x ends up with the same data as y, we don't care.
Copying x to y causes y to receive all the data that was in x, and x is left in a valid state (assuming the copy operation follows conventions and is not buggy). Thus, copying counts as moving. The reason for defining move operations in addition to copy operations is not to permit something new, but to permit greater efficiency in some cases. Anything that can be copied can be moved unless you take steps to prevent moves.
So what I see is A is a non-moveable class because of the definition of its copy control operations so it can only be copied and any attempt to move an object of this class, the corresponding copy operation is used instead.
What I see is that A is a moveable class (despite the lack of move constructor and move assignment), because of the definition of its copy control operations. Any attempt to move an object of this class will fall back on the corresponding copy operation. If you want a class to be copyable but not movable, you need to delete the move operations, while retaining the copy ones. (Try it. Add A(A&&) = delete; to your definition of A.)
The B class has one member that can be moved or copied, and one member that can be moved but not copied. So B itself can be moved but not copied. When B is moved, the unique_ptr member will be moved as you expect, and the A member will be copied (the fallback for moving objects of type A).
Things get more worse for me: if I uncomment the lines of move operations in B, the initialization above will not compile complaining about referencing a deleted funtion, the same thing for the assignment!
Read the error message more closely. When I replicated this result, the "use of deleted function" error was followed by a note providing more details: the move constructor was deleted because "its exception-specification does not match the implicit exception-specification". Removing the noexcept keywords allowed the code to compile (using gcc 9.2 and 6.1).
Alternatively, you could add noexcept to the copy constructor and copy assignment of A (keeping noexcept on the move operations of B). This is one way to demonstrate that the default move operations of B use the copy operations of A.
Here is a summary of #JaMiT's excellent answer:
Class A is moveable via it's copy-constructor and copy-assignment operator, even though class A is not MoveConstructible and is not MoveAssignable. See the notes on cppreference.com's pages for MoveConstructible and MoveAssignable.
And thus class B is also moveable.
The language allows you to prevent moveability for class A by explicitly =delete'ing the move-constructor and move-assignment, even though class A is still copyable.
Is there a practical reason to have a copyable but not-moveable class? Someone asked just this question several years ago here. The answers and comments struggled to find any practical reason to want a copyable but not-moveable class.
std::move does not force object to be copied. It just returns &&-reference (which allows compiler to use move ctor/assign operator).
In cases 1,2 object is copied.
In 3,4 cases (i think) object is moved. But A is still copied because it cannot be moved.
Why am I allowed to use std::move on a class which contains fields of a type with deleted move semantics (case 1), but am not allowed to use it on an instance of such a class (case 2)?
I understand case 2. I have explicitly deleted the move constructor, so I get an error if I try to move it. But I would expect that this would also be the case in case 1, where such class is also being moved.
class TestNonMovable {
std::string ss;
public:
TestNonMovable(){}
TestNonMovable(const TestNonMovable&) {}
TestNonMovable(TestNonMovable&&) = delete;
};
class SomeClass {
TestNonMovable tnm;
};
int main() {
// case1: This compiles, my understanding is that for SomeClass::tnm compiler will use copy constrctor
SomeClass sc1;
SomeClass sc2 = std::move(sc1);
// case2: This does not compile, move constructor is explicitly deleted, compiler will not try using copy constructor
TestNonMovable tnm;
TestNonMovable tnm2 = std::move(tnm); //error: use of deleted function 'TestNonMovable::TestNonMovable(TestNonMovable&&)'
}
Note the difference between the two classes. For TestNonMovable (case 2), you explicitly declare the move constructor as delete. With TestNonMovable tnm2 = std::move(tnm); the deleted move constructor is selected in overload resolution and then cause the error.
For SomeClass (case 1), you don't explicitly declare the move and copy constructor. The copy constructor will be implicitly declared and defined, but the move constructor will be implicitly declared and defined as deleted because it has a non-movable data member. Note the deleted implicitly declared move constructor won't participate in overload resolution. With SomeClass sc2 = std::move(sc1);, the copy constructor is selected and then the code works fine. The rvalue reference returned from std::move could be bound to lvalue-reference to const (i.e. const SomeClass&) too.
The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue). (since C++14)
BTW: std::move itself is allowed in both cases. It doesn't perform the move operation, it just converts the argument to rvalue. The move operation happens at construction in both cases.
What is the rationale behind the different treatment of implicitly and explicitly deleted move constructors in the C++11 standard, with respect to the implicit generation of move constructors of containing/inheriting classes?
Do C++14/C++17 change anything? (Except DR1402 in C++14)
Note: I understand what is happening, I understand that it is according to the C++11 standard's rules, I'm interested in the rationale for these rules that imply this behavior (please make sure not to simply restate that it is the way it is because the standard says so).
Assume a class ExplicitDelete with an explicitly deleted move ctor and an explicitly defaulted copy ctor. This class isn't move constructible even though a compatible copy ctor is available, because overload resolution chooses the move constructor and fails at compile time due to its deletion.
Assume a class ImplicitDelete which either contains or inherits from ExplicitDelete and does nothing else. This class will have its move ctor implicitly declared as deleted due to C++11 move ctor rules. However, this class will still be move constructible via its copy ctor. (Does this last statement have to do with resolution of DR1402?)
Then a class Implicit containing/inheriting from ImplicitDelete will have a perfectly fine implicit move constructor generated, that calls ImplicitDelete's copy ctor.
So what is the rationale behind allowing Implicit to be able to move implicitly and ImplicitDelete not to be able to move implicitly?
In practice, if Implicit and ImplicitDelete have some heavy-duty movable members (think vector<string>), I see no reason that Implicit should be vastly superior to ImplicitDelete in move performance. ImplicitDelete could still copy ExplicitDelete from its implicit move ctor—just like Implicit does with ImplicitDelete.
To me, this behavior seems inconsistent. I'd find it more consistent if either of these two things happened:
The compiler treats both the implicitly and explicitly deleted move ctors the same:
ImplicitDelete becomes not move-constructible, just like ExplicitDelete
ImplicitDelete's deleted move ctor leads to a deleted implicit move ctor in Implicit (in the same way that ExplicitDelete does that to ImplicitDelete)
Implicit becomes not move-constructible
Compilation of the std::move line utterly fails in my code sample
Or, the compiler falls back to copy ctor also for ExplicitDelete:
ExplicitDelete's copy constructor is called in all moves, just like for ImplicitDelete
ImplicitDelete gets a proper implicit move ctor
(Implicit is unchanged in this scenario)
The output of the code sample indicates that the Explicit member is always moved.
Here's the fully working example:
#include <utility>
#include <iostream>
using namespace std;
struct Explicit {
// prints whether the containing class's move or copy constructor was called
// in practice this would be the expensive vector<string>
string owner;
Explicit(string owner) : owner(owner) {};
Explicit(const Explicit& o) { cout << o.owner << " is actually copying\n"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is moving\n"; }
};
struct ExplicitDelete {
ExplicitDelete() = default;
ExplicitDelete(const ExplicitDelete&) = default;
ExplicitDelete(ExplicitDelete&&) noexcept = delete;
};
struct ImplicitDelete : ExplicitDelete {
Explicit exp{"ImplicitDelete"};
};
struct Implicit : ImplicitDelete {
Explicit exp{"Implicit"};
};
int main() {
ImplicitDelete id1;
ImplicitDelete id2(move(id1)); // expect copy call
Implicit i1;
Implicit i2(move(i1)); // expect 1x ImplicitDelete's copy and 1x Implicit's move
return 0;
}
So what is the rationale behind allowing Implicit to be able to move implicitly and ImplicitDelete not to be able to move implicitly?
The rationale would be this: the case you describe does not make sense.
See, all of this started because of ExplicitDelete. By your definition, this class has an explicitly deleted move constructor, but a defaulted copy constructor.
There are immobile types, with neither copy nor move. There are move-only types. And there are copyable types.
But a type which can be copied but has an explicitly deleted move constructor? I would say that such a class is a contradiction.
Here are the three facts, as I see it:
Explicitly deleting a move constructor is supposed to mean you can't move it.
Explicitly defaulting a copy constructor is supposed to mean you can copy it (for the purposes of this conversation, of course. I know you can still do things that make the explicit default deleted instead).
If a type can be copied, it can be moved. That's why the rule about implicitly deleted move constructors not participating in overload resolution exists. Therefore, movement is a proper subset of copying.
The behavior of C++ in this instance is inconsistent because your code is contradictory. You want your type to be copyable but not moveable; C++ does not allow that, so it behaves oddly.
Look at what happens when you remove the contradiction. If you explicitly delete the copy constructor in ExplicitDelete, everything makes sense again. ImplicitDelete's copy/move constructors are implicitly deleted, so it is immobile. And Implicit's copy/move constructors are implicitly deleted, so it too is immobile.
If you write contradictory code, C++ will not behave in an entirely legitimate fashion.
I've already tried to ask this question but I wasn't clear enough. So here is one more try. And I am very sorry for my English ;)
Let's see the code:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
void printRef() {
if (ref.get())
cout<<"i="<<*ref<<endl;
else
cout<<"i=NULL"<<endl;
}
A(const int i) : ref(new int(i)) {
cout<<"Constructor with ";
printRef();
}
~A() {
cout<<"Destructor with";
printRef();
}
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
It can not be compiled because unique_ptr has deleted copying constructor.
Orly?!
This class DOES HAVE an implied moving constructor because unique_ptr has one.
Let's do a test:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
void printRef() {
if (ref.get())
cout<<"i="<<*ref<<endl;
else
cout<<"i=NULL"<<endl;
}
A(const int i) : ref(new int(i)) {
cout<<"Constructor with ";
printRef();
}
// Let's add a moving constructor.
A(A&& a) : ref(std::move(a.ref)) {
cout<<"Moving constructor with";
printRef();
}
~A() {
cout<<"Destructor with";
printRef();
}
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
I've added a moving constructor and now the code can be compiled and executed.
Even if the moving constructor is not used.
The output:
Constructor with i=0
Constructor with i=1
Destructor withi=1
Destructor withi=0
Okay...Let's do one more test and remove the copying constructor (but leave the moving one).
I don't post the code, there only one line has been added:
A(const A& a) = delete;
You should trust me - it works. So the compiler doesn't require a copying constructor.
But it did! (a facepalm should be here)
So what's going on? I see it completely illogical! Or is there some sort of twisted logic I don't see?
Once more:
unique_ptr has a moving constructor and has a deleted copying constructor. Compiler requires copying constructor to be present. But in fact the compiler requires a moving constructor (even if it is not used) and doesn't require a copying (because it could be deleted). And as I see the moving constructor is (should be?) present impliedly.
What's wrong with that?
P.S. One more thing - if I delete the moving constructor the program could not be compiled. So the moving constructor is required, but not the copying one.Why does it require copy-constructor if it's prohibited to use it there?
P.P.S.
Big thanks to juanchopanza's answer! This can be solved by:
A(A&& a) = default;
And also big thanks to Matt McNabb.
As I see it now, the moving constructor is absent because unique_ptr doesn't have a copying one the class has a destructor (and the general rule is that default/copying/moving constructors and destructor could be generated by default only all together). Then the compiler doesn't stop at moving one (why?!) and falls back to copying one. At this point the compiler can't generate it and stops with an error (about the copy constructor) when nothing else can be done.
By the way it you add:
A(A&& a) = delete;
A(const A& a) = default;
It could NOT be compiled with error about 'A::A(A&& a)' deletion, There will be no fall back to copying constructor.
P.P.P.S The last question - why does it stop with error at the COPY constructor but not the MOVE constructor?!
GCC++ 4.7/4.8 says: "error: use of deleted function ‘A::A(const A&)’"
So it stops at copy constructor.
Why?! There should be 'A::A(A&&)'
Ok. Now it seems like a question about move/copy constrcutor choosing rule.
I've created the new more specific question here
This is called copy elision.
The rule in this situation is that a copy/move operation is specified, but the compiler is allowed to optionally elide it as an optimization, even if the copy/move constructor had side-effects.
When copy elision happens, typically the object is created directly in the memory space of the destination; instead of creating a new object and then copy/moving it over to the destination and deleting the first object.
The copy/move constructor still has to actually be present, otherwise we would end up with stupid situations where the code appears to compile, but then fails to compile later when the compiler decides not to do copy-elision. Or the code would work on some compilers and break on other compilers, or if you used different compiler switches.
In your first example you do not declare a copy nor a move constructor. This means that it gets an implicitly-defined copy-constructor.
However, there is a rule that if a class has a user-defined destructor then it does not get an implicitly-defined move constructor. Don't ask me why this rule exists, but it does (see [class.copy]#9 for reference).
Now, the exact wording of the standard is important here. In [class.copy]#13 it says:
A copy/move constructor that is defaulted and not defined as deleted is implicitly defined if it is odr-used (3.2)
[Note: The copy/move constructor is implicitly defined even if the implementation elided its odr-use (3.2, 12.2). —end note
The definition of odr-used is quite complicated, but the gist of it is that if you never attempt to copy the object then it will not try to generate the implicitly-defined copy constructor (and likewise for moving and move).
As T.C. explains on your previous thread though, the act of doing A a[2] = {0, 1}; does specify a copy/move, i.e. the value a[0] must be initialized either by copy or by move, from a temporary A(0). This temporary is able to undergo copy elision, but as I explain earlier, the right constructors must still exist so that the code would work if the compiler decides not to use copy elision in this case.
Since your class does not have a move constructor here, it cannot be moved. But the attempt to bind the temporary to a constructor of A still succeeds because there is a copy-constructor defined (albeit implicitly-defined). At that point, odr-use happens and it attempts to generate the copy-constructor and fails due to the unique_ptr.
In your second example, you provide a move-constructor but no copy-constructor. There is still an implicitly-declared copy-constructor which is not generated until it is odr-used, as before.
But the rules of overload resolution say that if a copy and a move are both possible, then the move constructor is used. So it does not odr-use the copy-constructor in this case and everything is fine.
In the third example, again the move-constructor wins overload resolution so it does not matter what how the copy-constructor is defined.
I think you are asking why this
A a[2] = { 0, 1 };
fails to compile, while you would expect it to compile because A may have a move constructor. But it doesn't.
The reason is that A has a member that is not copyable, so its own copy constructor is deleted, and this counts as a user declared copy constructor has a user-declared destructor.
This in turn means A has no implicitly declared move constructor. You have to enable move construction, which you can do by defaulting the constructor:
A(A&&) = default;
To check whether a class is move constructible, you can use is_move_constructible, from the type_traits header:
std::cout << std::boolalpha;
std::cout << std::is_move_constructible<A>::value << std::endl;
This outputs false in your case.
The twisted logic is that you are supposed to write programs at a higher abstraction level. If an object has a copy constructor it can be copied, otherwise it cannot. If you tell the compiler this object shall not be copied it will obey you and not cheat. Once you tell it that it can be copied the compiler will try to make the copy as fast as possible, usually by avoiding the copy constructor.
As for the move constructor: It is an optimization. It tends to be faster to move an object from one place to another than to make an exact copy and destroy the old one. This is what move constructors are for. If there is no move constructor the move can still be done with the old fashioned copy and destroy method.
If I inhibit the move constructor in a class, I can no longer use it in a vector:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(Foo&&) = delete;
int i_;
};
int main()
{
std::vector<Foo> foo;
foo.push_back(Foo(1));
}
Why is this so?
Summary
Don't delete the move members.
Assuming your compiler is completely C++11 conforming, then explicitly deleting the move constructor will also implicitly declare the following:
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
That is if you declare a move constructor (or move assignment operator), and do not declare copy members, they are implicitly declared as deleted. So your complete class Foo is as if:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(Foo&&) = delete;
Foo(const Foo&) = delete; // implicitly declared
Foo& operator=(const Foo&) = delete; // implicitly declared
int i_;
};
Now vector<Foo>::push_back(Foo(1)) requires that Foo be MoveConstructible. MoveConstructible could be satisfied by an accessible move constructor, or even by an accessible copy constructor. But Foo has neither. To fix you could:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(const Foo&) = default;
Foo& operator=(const Foo&) = default;
int i_;
};
I.e. default the copy members and remove the deleted move member.
In general it is not a good idea to explicitly delete the move members. If you want a class to be copyable but not "movable", just declare exactly as you would in C++03: declare/define your copy members. You can let the copy members be compiler-generated with = default, and that still counts as a user-declaration. And don't declare move members. Move members that don't exist are not the same as deleted move members.
Deleted move members mean you can not construct a copy of Foo from an rvalue, even if the copy constructor would have worked fine to do so. This is rarely the desired intent.
Even if you want your class to not be copyable nor movable, it is better to just delete the copy members and leave the move members undeclared (meaning they won't exist). If you're ever reviewing code (including your own), and see deleted move members, they are almost certainly incorrect, or at the very best superfluous and confusing.
Some day someone will come up with a good use case for deleted move members. But it will be a rare use case. If you see such a pattern in code, you should expect the code author to have a very good explanation. Otherwise, deleted move members are likely to just be incorrect (at best superfluous). But on the bright side this error will show itself at compile time, instead of at run time (as in your example).
Here is a summary chart of what the compiler will implicitly do when you explicitly declare any of the special members. Those squares colored red represent deprecated behavior.
= default and = delete count as user-declared.
Click here if you would like to view the full slide deck.
You say:
I can no longer use it in a vector:
However, since C++11, requirements for use in a vector are minimal; instead, each vector operation has its own requirements. So, in fact you can use Foo in a vector but you are restricted to operations that have no possibility of requiring the object to be moved or copied.
For example you can write:
std::vector<Foo> w(5);
w.pop_back();
and you can also call w[0].some_member_function(); and so on.
However you cannot write any of the following, due to the way you defined Foo:
std::vector<Foo> w = { 1, 2, 3 }; // Requires moving elements out of initializer_list
w.resize(5); // Resize may mean reallocation which may require elements to be moved to new location
w.push_back(5); // ditto
w.erase(w.begin()); // Erasing an element may require other elements to move to fill its place
w[0] = 1; // operator= is implicitly deleted as described in Howard's answer (but you could explicitly default it to make this work)
If you want to have Foo be copyable, but not movable -- and also have any move scenarios fall back to using a copy -- then the only way to do that is to not declare any move-constructor at all; and force inhibition of the compiler-generated move-constructor by declaring a destructor, copy-constructor, and/or copy-assignment operator as shown in Howard's table.
See the last code sample in Howard's answer for an example of this.
To be clear there are actually several different possible statuses for the move constructor, not all shown in Howard's table:
Defined as deleted, and user-defined
Defined as deleted, and not user-defined (this happens when a member is not movable)
Defaulted and user-defined
Defaulted and not user-defined
User-provided (i.e. user-defined and neither defaulted nor deleted)
Not declared
In cases 1, 3, 4, 5 above, the move constructor is found by overload resolution. In cases 2 and 6 it is not found by overload resolution; and a copy/move scenario such as push_back(Foo(1)); will fall back to a copy constructor (if any).