问题
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!
回答1:
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
.
回答2:
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.
回答3:
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.
来源:https://stackoverflow.com/questions/60086366/implicit-move-vs-copy-operations-and-containment