如果我有一个向量(或类似的东西)成员变量,移动构造函数会是什么样子?

How does the move constructor look like if I have a vector (or anything like it) member variable?

标题几乎概括了我的问题。更详细地说:我知道当我在 C++11 中声明一个移动构造函数和一个移动赋值运算符时,我必须 "make the other objects variables zero"。但是,当我的变量不是 array 或简单的 intdouble 值,而是更 "complex" 类型时,这是如何工作的?

在这个例子中,我有一个 Shoplist class 和一个 vector 成员变量。我是否必须在移动赋值运算符和构造函数中调用 vector class 的析构函数?或者什么?

class Shoplist {
public:
    Shoplist() :slist(0) {};
    Shoplist(const Shoplist& other) :slist(other.slist) {};
    Shoplist(Shoplist&& other) :slist(0) {
        slist = other.slist;
        other.slist.~vector();
    }

    Shoplist& operator=(const Shoplist& other);
    Shoplist& operator=(Shoplist&& other);



    ~Shoplist() {};
private:
    vector<Item> slist;
};

Shoplist& Shoplist::operator=(const Shoplist& other)
{
    slist = other.slist;
    return *this;
}

Shoplist& Shoplist::operator=(Shoplist&& other)
{
    slist = other.slist;
    other.slist.~vector();
    return *this;
}

The move constructor 应该按成员移动:

Shoplist(Shoplist&& other)
    : slist(std::move(other.slist)) 
{}

请注意,编译器通过成员移动为您生成移动构造函数(如果可能),就像您在上面手动执行的那样。

无论 std::vector 需要做什么才能正确移动,都将由它自己的 move constructor 处理。

因此,假设您想要移动成员,直接使用它即可:

Shoplist(Shoplist&& other)
  : slist(std::move(other.slist))
{}

Shoplist& Shoplist::operator=(Shoplist&& other)
{
    slist = std::move(other.slist);
    return *this;
}

在这种情况下,您可以像 AndyG 指出的那样,只需使用 = default 让编译器为您生成完全相同的移动构造函数和移动赋值运算符。

请注意,像您那样明确地销毁原件绝对是绝对错误的。当 other 超出范围时,other 成员将再次被销毁


编辑:我确实说过 假设你想移动成员,因为在某些情况下你可能不会。

通常,如果数据成员在逻辑上属于 class,您希望像这样移动数据成员,并且移动比复制便宜得多。虽然 std::vector 移动肯定比复制便宜,但如果它包含一些临时缓存或临时值,而这些缓存或临时值在逻辑上不是对象标识或值的一部分,您可能会合理地选择丢弃它。

实施 copy/move/destructor 操作没有意义,除非您的 class 正在 管理 资源。通过管理资源,我的意思是直接负责它的生命周期:显式创建和销毁。 0的规则和3/5的规则源于这个简单的想法。

您可能会说您的 class 正在管理 slist,但在这种情况下这是错误的:std::vector class 直接(并且正确)管理与之相关的资源。如果让我们的class有隐含的cpy/mvctos/assignment和dtors,它们会正确调用相应的std::vector操作。所以你绝对不需要明确定义它们。在您的情况下,适用 0 规则。


I know that when I declare a move constructor and a move assignment operator in C++11 I have to "make the other objects variables zero"

嗯,不,不是真的。这个想法是,当你从一个对象移动时(阅读:从一个对象移动它的资源)然后你必须确保你的对象知道它拥有的资源不再属于它的所有权(例如,这样,它不会尝试在它的析构函数中释放它)。在 std::vector 的情况下,它的 move ctor 会将它指向内部缓冲区的指针设置为 nullptr.

I know that when I declare a move constructor and a move assignment operator in C++11 I have to "make the other objects variables zero"

这不太正确。您必须做的是保持从对象移出的有效性。这意味着你必须满足 class 不变量。

如果您为特定的 class 指定了特殊的不变量,这要求您将成员变量设置为零,那么 class 可能必须这样做。但这不是一般搬家的要求。


Do I have to invoke the destructor of the vector class in the move assignment operator and constructor?

绝对不是。 moved from 对象被销毁时,将调用成员的析构函数。

您通常会做的是移动 construct/assign 包含对象的移动 constructor/assignment 运算符中的每个成员。这就是隐式生成的特殊成员函数所做的。当然,这可能不满足所有 classes 的 class 不变量,如果不满足,那么您可能需要编写自己的版本。


如果您不尝试自己声明特殊成员函数,编译器将为您隐式生成它们。这是 class:

的最小但正确的版本
class Shoplist {
    vector<Item> slist;
};

这个class默认是可构造的、可移动的和可复制的。

允许移动构造函数(但不需要)"steal"移动对象的内容。这 而不是 意味着他们必须 "make the other objects variables zero"。例如,移动原始类型等同于复制它。它的意思是移动构造函数可以转移堆或自由存储中数据的所有权。在这种情况下,必须修改移出对象,以便在它被销毁时(在移动构造函数中应该 而不是 发生),它以前拥有的数据(在它被转移之前) ) 不会被释放。

Vector 提供了自己的移动构造函数。因此,为了为包含向量的对象编写正确的移动构造函数,您需要做的就是确保调用正确的向量构造函数。这是通过使用 std::move 将右值引用显式传递给子对象构造函数来完成的:

Shoplist(Shoplist&& other) :slist(std::move(other.slist)) {
   //... Constructor body 

...但实际上你一般不需要这样做。如果您不声明它们并且不声明析构函数,您的复制和移动构造函数将正确地自动生成。 (以下这种做法叫做"rule of 0"。)

或者,您可以强制编译器自动生成移动构造函数:

Shoplist(Shoplist&& other) = default;