移动具有不可移动成员的类型

huangapple go评论72阅读模式
英文:

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 &lt;iostream&gt;
class Copyable {
std::string name;
public:
explicit Copyable(std::string name): name(std::move(name)) {}
// Copying enabled
Copyable(const Copyable&amp; other): name(other.name) {
std::cout &lt;&lt; &quot;Copyable copy ctor&quot; &lt;&lt; std::endl;
}
Copyable&amp; operator=(const Copyable&amp; other) {
name=other.name;
std::cout &lt;&lt; &quot;Copyable op=&quot; &lt;&lt; std::endl;
return *this;
}
// Moving disabled with no copy fallback
Copyable(Copyable&amp;&amp;) = delete;
Copyable&amp; operator=(Copyable&amp;&amp;) = delete;
};
class Movable {
std::string name;
public:
explicit Movable(std::string name): name(std::move(name)) {}
// Copying enabled
Movable(const Movable&amp; other): name(other.name) {
std::cout &lt;&lt; &quot;Movable copy ctor&quot; &lt;&lt; std::endl;
}
Movable&amp; operator=(const Movable&amp; other) {
name=other.name;
std::cout &lt;&lt; &quot;Movable op=&quot; &lt;&lt; std::endl;
return *this;
}
// Moving enabled
Movable(Movable&amp;&amp; other) noexcept: name(std::move(other.name)) {
std::cout &lt;&lt; &quot;Movable move ctor&quot; &lt;&lt; std::endl;
}
Movable&amp; operator=(Movable&amp;&amp; other) noexcept {
name = std::move(other.name);
std::cout &lt;&lt; &quot;Movable move op=&quot; &lt;&lt; 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{&quot;copyable&quot;};
Movable m{&quot;movable&quot;};
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&amp;&amp; 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&amp;&amp; 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.

huangapple
  • 本文由 发表于 2023年6月19日 16:14:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76504799.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定