5

考虑到当今编译器在返回值优化方面的高质量(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);
    ...
};

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

4

5 回答 5

9

出于纯粹的语义原因,它已经很有意义了,即使您将优化方面完全排除在外。试想一个类不/不能实现复制的情况。例如boost::scoped_ptr不能复制,但可以移动!

于 2011-01-18T13:00:14.093 回答
7

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

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

如果你的类没有用户声明的拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数,编译器会为你提供默认的移动构造函数和移动赋值运算符。这些默认实现将简单地按成员移动所有内容。

您还可以显式默认您的移动成员:

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

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

在一个密切相关的最新更改中:如果您的类具有显式析构函数和隐式复制成员,则这些成员的隐式生成现在已被弃用(委员会希望在存在显式析构函数时删除隐式生成副本,但不能因为向后兼容)。

所以总而言之,任何时候你声明了一个析构函数,你可能应该考虑明确声明复制和移动成员。

于 2011-01-23T19:34:24.423 回答
5

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

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

  • 涉及动态内存分配
  • move 对一个或多个成员变量有意义(这意味着它们涉及沿线某处的动态分配)。
于 2011-01-18T12:49:29.923 回答
5

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

于 2011-01-18T12:58:13.243 回答
2

不管编译器可能自动完成的任何事情,我都会说:

  • 如果任何成员具有有意义和有益的移动语义,则该类也应该具有这种移动语义。(->std::vector成员)
  • 如果复制时涉及任何动态分配,则移动操作是有意义的。

换句话说,如果 move 可以做比 copy 更有效的事情,那么添加它是有意义的。在你really_trivial的行动中,只能和副本一样有效。

于 2011-01-18T12:50:08.177 回答