26


考虑到以下类,实现移动构造函数的正确方法是什么:

class C {
public:
    C();
    C(C&& c);
private:
    std::string string;
}

当然,这个想法是避免复制string或释放它两次。
让我们假设基本示例只是为了清楚起见,我确实需要一个移动构造函数。


我试过:

C::C(C&& c) {
    //move ctor
    string = std::move(c.string);
}

C::C(C&& c) : string(std::move(c.string)) {
    //move ctor
}

两者都可以在 gcc 4.8 上编译并运行良好。似乎选项 A正确的行为,string被复制而不是使用选项 B 移动。
这是移动构造函数的正确实现吗?

4

4 回答 4

15

由于std::string它本身有一个 move-ctor,隐式定义的 move-ctor forC将负责正确的移动操作。您可能无法自己定义。但是,如果您有任何其他数据成员,特别是:

12.8 复制和移动类对象

12隐式声明的复制/移动构造函数是其类的内联公共成员。如果 X 具有以下属性,则类 X 的默认复制/移动构造函数被定义为已删除 (8.4.3):

— 具有非平凡对应构造函数的变体成员,并且 X 是类联合类,

— 类类型 M(或其数组)的非静态数据成员,不能复制/移动,因为重载决议 (13.3) 应用于 M 的相应构造函数,导致歧义或函数从默认构造函数,或

— 无法复制/移动的直接或虚拟基类 B,因为应用于 B 的相应构造函数的重载决议 (13.3) 会导致歧义或从默认构造函数中删除或无法访问的函数,或

— 对于移动构造函数,非静态数据成员或直接或虚拟基类,其类型没有移动构造函数且不可轻易复制。

13类 X 的复制/移动构造函数是微不足道的,如果它既不是用户提供的也不是删除的,并且如果

— X 类没有虚函数 (10.3) 和虚基类 (10.1),函数 (10.3) 和虚基类 (10.1),以及

— 选择复制/移动每个直接基类子对象的构造函数是微不足道的,并且

— 对于 X 的每个类类型(或其数组)的非静态数据成员,选择复制/移动该成员的构造函数是微不足道的;否则复制/移动构造函数是不平凡的。

您可能想要实现自己的 move-ctor。

如果您需要 move-ctor,请首选初始化列表语法。总是!否则,您最终可能会得到初始化列表中未提及的每个对象的默认构造(对于仅具有非默认 ctor 的成员对象,您必须这样做)。

于 2012-05-01T18:27:59.810 回答
11

您的两个变体都会移动字符串。第二个变体应该是首选,因为它不会默认构造一个空字符串,只是为了在之后移动分配它。

检查您的测试用例,然后检查您的编译器的 bugzilla 列表。如果您想确保这两种情况都移动,则需要跟踪对string::operator=(string&&)(第一种情况)和(第二种情况)的调用。 string::string(string&&)

于 2012-05-01T18:31:02.243 回答
3

两个构造函数都应该工作。所以两者都是正确的移动构造函数。第二个可能更有效,因为第一个默认构造string仅分配给它,而第二个将简单地移动构造它,因此应该更有效。如果第二个效率较低,我会怀疑是编译器错误(请记住,对于当前的编译器,C++11 支持仍然不完整)或有缺陷的测试方法(你如何测试复制与移动,你确定移动构造函数不是在这两种情况下都调用了赋值操作?)。

当然,只要有可能,您就可以简单地让编译器通过C(C&&) = default;.

于 2012-05-01T18:32:22.360 回答
2

无需在此处实现移动构造函数,因为您不必手动管理内存。移动构造函数仅在您在类中手动使用动态数组时才有用。

您仍然可以显式地让编译器创建默认的移动构造函数,即使它应该已经完成​​,即使您没有请求它:

C(C&& c) = default;
于 2012-05-01T18:24:56.683 回答