In his "C++ Move Semantics" book Nicolai M. Josuttis states that moving an object that contains a non-movable member amongst movable members with the generated move constructor move all members but the non-movable one (which is copied). Below is code snippet which is a variation of the example in the book.
#include <iostream>
class Copyable {
std::string name;
public:
explicit Copyable(std::string name): name(std::move(name)) {}
// Copying enabled
Copyable(const Copyable& other): name(other.name) {
std::cout << "Copyable copy ctor" << std::endl;
}
Copyable& operator=(const Copyable& other) {
name=other.name;
std::cout << "Copyable op=" << std::endl;
return *this;
}
// Moving disabled with no copy fallback
Copyable(Copyable&&) = delete;
Copyable& operator=(Copyable&&) = delete;
};
class Movable {
std::string name;
public:
explicit Movable(std::string name): name(std::move(name)) {}
// Copying enabled
Movable(const Movable& other): name(other.name) {
std::cout << "Movable copy ctor" << std::endl;
}
Movable& operator=(const Movable& other) {
name=other.name;
std::cout << "Movable op=" << std::endl;
return *this;
}
// Moving enabled
Movable(Movable&& other) noexcept: name(std::move(other.name)) {
std::cout << "Movable move ctor" << std::endl;
}
Movable& operator=(Movable&& other) noexcept {
name = std::move(other.name);
std::cout << "Movable move op=" << std::endl;
return *this;
}
};
class Container {
Copyable copyable;
Movable movable;
public:
Container(Copyable copyable, Movable movable): copyable(copyable), movable(std::move(movable)) {}
// Both copying and moving enabled by default
};
int main() {
Copyable c{"copyable"};
Movable m{"movable"};
Container container{c, m};
Container container2{std::move(container)};
}
Compiled with GCC on x86-64 with C++17 standard following output is produced:
Container created and initialized:
Copyable copy ctor
Movable copy ctor
Copyable copy ctor
Movable move ctor
Container moved:
Copyable copy ctor
Movable copy ctor
No move ctor is called for the movable member once the container is moved.
According to the book move ctor should be called for the Movable member, shouldn’t it?
>Solution :
I’m not sure what the author of the book meant, but here is a quote from cppreference:
The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true:
- T has non-static data members that cannot be moved (have deleted, inaccessible, or ambiguous move constructors);
- […]
So the move constructor of Container
is implicitly-declared (aka "generated") as deleted. You cannot use it. Instead, std::move(container)
binds to the const reference of the copy constructor and that is what is called.
Note that if you try to declare that move constructor as default like so, the compiler should give you an error message:
Container(Container&& other) noexcept = default;
For example, GCC says:
error: use of deleted function ‘Container::Container(Container&&)’
note: ‘Container::Container(Container&&)’ is implicitly deleted because the default definition would be ill-formed
You can get the behavior described in the book, but you have to write it yourself. Something like this:
Container(Container&& other) :
copyable(other.copyable),
movable(std::move(other.movable)) {
}
… though I don’t know why you would ever want to do that. In a concrete scenario, there must be a good reason for not being able to move Copyable
. To be fair, a copyable-but-not-movable type does not seem to be very useful. Still, I would not expect the behavior described in the book to be the default (implicit) one.