英文:
Moving a type with non-movable member
问题
在他的《C++ Move Semantics》一书中,Nicolai M. Josuttis指出,移动包含一个不可移动成员的对象到可移动成员之间,生成的移动构造函数会移动所有成员,但不可移动的成员会被复制。以下是书中示例的变种代码。
#include <iostream>
class Copyable {
std::string name;
public:
explicit Copyable(std::string name): name(std::move(name)) {}
// 允许拷贝
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;
}
// 禁用移动,没有复制备用
Copyable(Copyable&&) = delete;
Copyable& operator=(Copyable&&) = delete;
};
class Movable {
std::string name;
public:
explicit Movable(std::string name): name(std::move(name)) {}
// 允许拷贝
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;
}
// 允许移动
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)) {}
// 默认情况下同时允许拷贝和移动
};
int main() {
Copyable c{"copyable"};
Movable m{"movable"};
Container container{c, m};
Container container2{std::move(container)};
}
使用C++17标准在x86-64上使用GCC编译,会产生以下输出:
容器创建并初始化:
Copyable copy ctor
Movable copy ctor
Copyable copy ctor
Movable move ctor
容器移动:
Copyable copy ctor
Movable copy ctor
一旦容器被移动,可移动成员就不再调用移动构造函数了。根据这本书的内容,可移动成员的移动构造函数应该被调用,不是吗?
英文:
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?
答案1
得分: 8
我不确定书的作者的意思,但是这里是来自cppreference的一句话:
类T的隐式声明或默认声明的移动构造函数在以下情况下被定义为已删除:
- T具有无法移动的非静态数据成员(具有已删除、不可访问或模糊的移动构造函数);
- [...]
因此,Container
的移动构造函数被隐式声明(也称为“生成”),并被定义为已删除。您无法使用它。相反,std::move(container)
绑定到复制构造函数的常量引用,这就是所调用的内容。
请注意,如果您尝试像这样将移动构造函数声明为默认值,编译器应该会给您一个错误消息:
Container(Container&& other) noexcept = default;
例如,GCC报告如下:
错误:使用已删除的函数 'Container::Container(Container&&)'
注:'Container::Container(Container&&)'被隐式删除,因为默认定义将无法形成
您可以获得书中描述的行为,但您必须自己编写它。类似这样的内容:
Container(Container&& other) :
copyable(other.copyable),
movable(std::move(other.movable)) {
}
尽管我不知道为什么您会希望这样做。在具体情景中,一定有一个不可以移动Copyable
的充分理由。公平地说,可复制但不可移动的类型似乎并不是很有用。不过,我不会期望书中描述的行为是默认(隐式)的行为。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论