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

时间:2011-01-18 12:37:15

标签: c++ c++11 rvalue-reference value-type move-semantics

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

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

class really_trivial
{
    int first_;
    int second_;

public:

    really_trivial();
    ...
};

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

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

public:

    semi_complex(semi_complex&& other);
    semi_complex& operator=(semi_complex&& other);
    ...
};

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

5 个答案:

答案 0 :(得分:8)

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

答案 1 :(得分:6)

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

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

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

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

semi_complex(semi_complex&&) = default;
semi_complex& operator=(semi_complex&&) = default;

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

在一个密切相关的最新变化中:如果你的类有一个显式的析构函数和隐式复制成员,那么这些成员的隐式生成现在已被弃用(委员会希望删除隐式生成的副本,当有一个显式析构函数,但由于向后兼容性不能)。

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

答案 2 :(得分:4)

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

因此,当

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

答案 3 :(得分:4)

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

答案 4 :(得分:1)

无论编译器可能自动完成任何操作,我都会说:

  • 如果任何成员具有有意义且有益的移动语义,那么该类也应具有此移动语义。 ( - &gt; std::vector成员)
  • 如果在复制时涉及任何动态分配,则移动操作是有意义的。

否则,如果移动可以比复制更有效地执行某些操作,则添加它是有意义的。在really_trivial中,移动只能与复制品一样有效。