何时添加移动构造函数和移动赋值运算符真的会有所作为?

Joh*_*ell 4 c++ value-type rvalue-reference move-semantics c++11

考虑到当今关于返回值优化(RVO和NRVO)的编译器的高质量,我想知道在开始添加移动构造函数和移动赋值运算符时实际有意义的类复杂性.

例如,对于这个really_trivial类,我只是假设移动语义不能提供比RVO更多的东西,而NRVO在复制它的实例时已经做了:

class really_trivial
{
    int first_;
    int second_;

public:

    really_trivial();
    ...
};
Run Code Online (Sandbox Code Playgroud)

在这个semi_complex类中,我会毫不犹豫地添加一个移动构造函数和移动赋值运算符:

class semi_complex
{
    std::vector<std::string> strings_;

public:

    semi_complex(semi_complex&& other);
    semi_complex& operator=(semi_complex&& other);
    ...
};
Run Code Online (Sandbox Code Playgroud)

那么,在添加移动构造函数和移动赋值运算符时,开始有意义的成员变量的数量和类型是什么?

ltj*_*jax 8

即使您完全保留优化方面,它在纯语义上也是有意义的.想象一下一个类没有/不能实现复制的情况.例如boost::scoped_ptr无法复制,但可以移动!


How*_*ant 6

除了已经给出的优秀答案之外,我还想补充一些前瞻性细节:

最新的C++ 0x草案中有新规则用于自动生成移动构造函数和移动赋值运算符.虽然这个想法并不是全新的,但最新的规则自2010年10月起才出现在草案中,并且尚未在编译器中广泛使用.

如果您的类没有用户声明的复制构造函数,复制赋值运算符,移动构造函数,移动赋值运算符和析构函数,编译器将为您提供默认的移动构造函数和移动赋值运算符.这些默认实现只是成员方式移动所有内容.

您还可以明确默认移动成员:

semi_complex(semi_complex&&) = default;
semi_complex& operator=(semi_complex&&) = default;
Run Code Online (Sandbox Code Playgroud)

请注意,如果这样做,除非明确提供或默认复制构造函数和复制赋值运算符,否则将隐式禁止复制语义.

在一个密切相关的最新变化中:如果您的类具有显式析构函数和隐式复制成员,那么现在不推荐使用这些成员的隐式生成(委员会希望在存在显式析构函数时删除隐式生成的副本,但不能因为向后兼容).

总而言之,每当你声明析构函数时,你应该考虑明确声明复制和移动成员.


Gri*_*zly 5

根据经验,只要您有成员变量保存(有条件地)动态分配的内存,我就会说添加一个移动构造函数。在这种情况下,如果您可以仅使用现有内存并为移动源提供所需的最小分配,那么它通常会更便宜,因此它仍然可以运行(意味着被破坏)。成员变量的数量并不那么重要,因为对于不涉及动态内存的类型,复制或移动它们不太可能产生影响(即使移动构造函数也必须以某种方式将它们从一个内存位置复制到另一个)。

因此移动语义是有意义的,当

  • 涉及动态内存分配
  • move 对一个或多个成员变量有意义(这意味着那些涉及沿线某处的动态分配)。


Pup*_*ppy 5

通常,当类拥有某种资源时,移动是有意义的,其中副本将涉及复制该资源而移动则不会。最简单的例子是动态分配内存。但是,值得注意的是,编译器会自动生成移动构造函数和运算符,就像复制一样。