如果我有一个向量(或任何类似的)成员变量,移动构造函数如何?

时间:2017-01-10 15:36:38

标签: c++ c++11 move-semantics

标题几乎总结了我的问题。更详细:我知道当我在C ++ 11中声明移动构造函数和移动赋值运算符时,我必须“使其他对象变量为零”。但是,当我的变量不是array或简单的intdouble值,而是一个更“复杂”的类型时,它是如何工作的?

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

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;
}

5 个答案:

答案 0 :(得分:5)

无论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让编译器生成完全相同的移动ctor并为您移动赋值运算符。

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

编辑:我确实说假设你要移动成员,因为在某些情况下你可能没有。

一般来说,如果数据成员在逻辑上属于类的一部分,那么你想移动这样的数据成员,并且移动比复制要便宜得多。虽然std::vector移动比复制更便宜,但如果它拥有一些暂时缓存或临时值,这些值在逻辑上不属于对象的身份或价值,您可以合理地选择丢弃它

答案 1 :(得分:2)

除非您的班级管理资源,否则实施复制/移动/析构函数操作是没有意义的。通过管理资源,我的意思是直接负责它的生命周期:明确的创建和破坏。规则0和3/5规则源于这个简单的概念。

您可能会说您的班级正在管理slist,但在此上下文中这是错误的:std::vector类直接(并且正确)地管理与之关联的资源。如果让我们的类具有隐式cpy / mv ctos / assignment和dtors,它们将正确调用相应的std::vector操作。所以你绝对不需要明确定义它们。在您的情况下,规则为0适用。

  

我知道当我声明移动构造函数和移动赋值时   C ++ 11中的运算符我必须使其他对象变量为零&#34;

唔不,不是真的。想法是当你从一个物体移动时(读取:从一个物体移动它的资源)然后你必须确保你的物体意识到它所拥有的资源不再是它下面的物体#39;所有权(例如,它不会试图在它的析构函数中释放它)。在std::vector的情况下,它的移动ctor会将它对内部缓冲区的指针设置为nullptr

答案 2 :(得分:1)

  

我知道当我在C ++ 11中声明一个移动构造函数和一个移动赋值运算符时,我必须&#34;使其他对象变量为零&#34;

这不太正确。你必须做的是保持从物体移动的有效性。这意味着您必须满足类不变量。

如果为特定类指定了一个特殊的不变量,那么需要将成员变量设置为零,那么这个类可能也必须这样做。但这并不是一般行动的要求。

  

我是否必须在移动赋值运算符和构造函数中调用向量类的析构函数?

绝对不是。当从对象移动时,将调用成员的析构函数。

您通常会做的是移动构造/分配包含对象的移动构造函数/赋值运算符中的每个成员。这是隐式生成的特殊成员函数所做的事情。当然,这可能不满足所有类的类不变量,如果它没有,那么你可能需要编写它们自己的版本。

如果您不尝试自己声明,编译器将隐式为您生成特殊成员函数。这是您班级的最小但正确的版本:

class Shoplist {
    vector<Item> slist;
};

此类是默认的可构造,可移动和可复制的。

答案 3 :(得分:0)

The move constructor应该按成员方式移动:

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

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

答案 4 :(得分:0)

移动构造函数允许(但需要)“窃取”移动对象的内容。这 not 意味着它们必须“使其他对象变量为零”。例如,移动基本类型相当于复制它。它的意思是移动构造函数可以转移堆或免费存储中的数据的所有权。在这种情况下,必须修改移动的对象,以便在它被销毁时(在移动构造函数中发生),它先前拥有的数据(在传输之前)不会被释放。

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

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

......但实际上你可能不需要这样做。如果您没有声明它们并且不声明析构函数,那么您的复制和移动构造函数将被正确地自动生成。 (遵循这种做法称为“0规则”。)

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

Shoplist(Shoplist&& other) = default;