因此,在观看了有关右值引用的精彩讲座之后,我认为每个类都会从这样的“移动构造函数”,template<class T> MyClass(T&& other)编辑以及Philipp在他的答案中指出的“移动赋值运算符”,template<class T> MyClass& operator=(T&& other)中受益。动态分配的成员,或通常存储指针。就像如果前面提到的要点一样,您应该拥有复制控制器,赋值运算符和析构函数。
有什么想法?

评论

可以在此处找到一些其他信息brookspatola.com/the-rule-of-five-c11(只是从已删除的答案中复制)

令人毛骨悚然。您与“精彩”演讲的链接。我从没看过这个系列。

#1 楼

我想说“三定律”变成了三,四和五的定律:


每个类都应明确定义以下一组特殊成员中的一个

/>函数:



析构函数,副本构造函数,副本赋值运算符

此外,每个明确定义析构函数的类都可以明确定义移动构造函数和/或移动赋值运算符。

通常,以下一组特殊成员函数
是明智的:


无(对于许多隐式生成的特殊成员函数正确且快速的简单类)
析构函数,副本构造函数,副本赋值运算符(在这种情况下,
类将不可移动)
析构函数, move构造函数,move赋值运算符(在这种情况下,该类将不可复制,对于基础资源不可复制的资源管理类很有用)
析构函数,copy constr uctor,复制赋值运算符,move构造函数(由于复制省略,如果复制赋值运算符按值接受其参数,则不会产生开销)
析构函数,复制构造函数,copy赋值运算符,move构造函数,
move赋值运算符



请注意,不会为显式声明任何其他特殊成员函数的类(复制构造函数和副本)生成move构造函数和move赋值运算符不会为显式声明了move构造函数或move赋值运算符的类生成赋值运算符,并且将具有显式声明的析构函数和隐式定义的副本构造函数或隐式定义的副本赋值运算符的类视为已弃用。特别是,以下完全有效的C ++ 03多态基类

class C {
  virtual ~C() { }   // allow subtype polymorphism
};


应重写为:

class C {
  C(const C&) = default;               // Copy constructor
  C(C&&) = default;                    // Move constructor
  C& operator=(const C&) = default;  // Copy assignment operator
  C& operator=(C&&) = default;       // Move assignment operator
  virtual ~C() { }                     // Destructor
};


有点烦人,但可能比其他方法更好(自动生成所有特殊成员函数)。

与三巨头规则相比,不遵守该规则会造成严重损害,没有明确声明move构造函数和move赋值运算符通常没问题,但在效率方面通常不是最佳的。如上所述,仅当没有显式声明的复制构造函数,复制赋值运算符或析构函数时,才会生成移动构造函数和移动赋值运算符。就复制构造函数和复制赋值运算符的自动生成而言,这与传统的C ++ 03行为不对称,但更为安全。因此,定义移动构造函数和移动赋值运算符的可能性非常有用,并且可以创建新的可能性(纯可移动类),但是遵循C ++ 03三巨头规则的类仍然可以。

对于资源管理类,如果无法复制基础资源,则可以将复制构造函数和复制赋值运算符定义为已删除(视为定义)。通常,您仍然需要移动构造函数和移动赋值运算符。与C ++ 03一样,通常使用swap实现复制和移动分配运算符。如果您具有move构造函数和move赋值运算符,则专门化std::swap将变得不重要,因为通用std::swap使用move构造函数和move赋值运算符(如果可用),并且应该足够快。

没有此类的类用于资源管理(即没有非空析构函数)或子类型多态性(即没有虚拟析构函数)的对象不应声明五个特殊成员函数中的任何一个;它们都会自动生成,并且行为正确且快速。

评论


@Philipp:嗯,对吧……如果我没错,只是实现了move-ctor,那么按值传递赋值运算符“ other”会被移动构造吗?我认为编译器会优化其余的指针副本和赋值...

– Xeo
2011年1月24日14:33

@Xeo:我相信,如果该类不可复制,那么即使可以删除该副本,也无法按值传递其实例。在那种情况下,您应该使用右值引用声明真正的移动赋值运算符(按值接受其参数的赋值运算符是§12.8/ 19的副本赋值运算符,如果类不可复制,则不希望使用该赋值运算符)。对于可复制和可移动类,编译器应使用复制省略或调用move构造函数。

– Philipp
2011年1月24日14:42

@Omni:声明时不能显式默认使用虚函数(第8.4.2 / 2节和第8.4.2 / 5节中的最后一个示例)。

– Philipp
2011年2月8日19:50

自从C ++ 11通过以来,规则有没有改变?我相信struct C {virtual〜C()= default; };现在允许使用,也是最简洁的选择。 n3290中不再存在来自n3242的禁止(“-不得为虚拟”),GCC允许,而以前则没有。

–吕克·丹顿(Luc Danton)
2011-09-21 18:18



@BЈовић不,这不是错字。这是一个很好的解释:stackoverflow.com/a/12306344/1174378

– Mihai Todor
2012年9月6日19:00

#2 楼

我不敢相信没有人链接到此文章。

基本文章主张“零规则”。
我不宜引用整篇文章,但我认为这是主要的点:


具有自定义析构函数,复制/移动构造函数或复制/移动赋值运算符的类应专门处理所有权。
其他类不应具有自定义析构函数,copy /移动
构造函数或复制/移动赋值运算符。


这一点也很重要,恕我直言:


常见的“所有权归属标准套件库中包含“ a-package”类:std::unique_ptrstd::shared_ptr。通过使用
自定义删除器对象,这两个对象已变得足够灵活,可以管理
几乎任何类型的资源。


评论


对于我在整个问题上的想法,请参见此处和此处。 :)

– Xeo
2012年12月19日在12:33

#3 楼

我不这么认为,“三”法则是一个经验法则,它指出实现以下其中一个但不是全部之一的类可能有错误。


复制构造函数
赋值运算符
析构函数

但是省略move构造函数或move赋值运算符并不意味着存在错误。 (在大多数情况下)优化可能是错过的机会,或者移动语义与此类无关,但这不是错误。

定义移动可能是最佳实践相关的构造函数,它不是强制性的。在许多情况下,move构造函数与某个类无关(例如std::complex),并且即使在C ++ 03中,所有未正确定义行为的类也将继续在C ++ 0x中正确行为。构造函数。

#4 楼

是的,我认为为此类提供一个move构造函数会很好,但是请记住:


这只是一种优化。
仅实现一个或两个副本构造函数,赋值运算符或析构函数可能会导致错误,而没有move构造函数只会潜在地降低性能。


Move构造函数不能总是在没有修改的情况下应用。类始终分配有其指针,因此此类始终在析构函数中删除其指针。在这些情况下,您需要添加额外的检查以说出它们的指针是否已分配或已移走(现在为空)。



评论


这不仅是一种优化,移动语义对于完美转发很重要,并且某些类(unique_ptr)如果没有移动语义就无法实现。

–小狗
2011年1月24日14:17

@DeadMG:一般来说,您是对的,但是在这种情况下,移动语义只是一种优化。在这里,我要说的是已经存在的尊重三个规则的类。 unique_ptr和完善转发是一些特殊情况...

– Peoro
2011年1月24日14:21

@peoro:这就像在暗示C ++仅向C添加类。auto_ptr遵循三个规则,而unique_ptr最不肯定遵循五个规则。

–小狗
2011年1月24日14:26

@peoro:我认为可以调用一个声明私有副本构造函数和副本赋值运算符(或从boost :: noncopyable继承)的C ++ 03类来遵守三法则。 (否则,我们必须引入不同的术语,例如“大一小规则”)。

– Philipp
2011年1月24日14:26

一些类始终分配有其指针...在这种情况下,通常将移动实现为交换。既简单又快速。 (实际上更快,因为它将重新分配移至右值的析构函数)

–鸭鸭
11年8月26日在19:11

#5 楼

这是自11年1月24日以来的当前状态和相关发展的简短更新。

根据C ++ 11标准(请参阅附录D的[depr.impldec]):


如果以下情况不赞成使用复制构造函数的隐式声明:类具有用户声明的副本分配运算符或用户声明的析构函数。如果该类具有用户声明的副本构造函数或用户声明的析构函数,则不赞成使用复制赋值运算符的隐式声明。


实际上是建议废弃赋予C的不赞成使用的行为。 ++ 14是真正的“五个规则”,而不是传统的“三个规则”。 2013年,专家工作组投票反对将在C ++ 2014中实施的该提案。对该提案做出决定的主要理由与对破坏现有代码的普遍担忧有关。

最近,又有人提出修改C ++ 11的措词以实现非正式的五规则,即


没有复制功能,move函数或析构函数由用户提供,如果其中任何一个由编译器生成。


如果由EWG批准,则C +可能会采用“规则” +17。

评论


感谢更新。随着这些C ++问题中的某些问题变老,查看新的语言版本对问题和/或答案的影响会很有帮助。

–cb4
16年4月2日在15:26

#6 楼

基本上是这样的:如果不声明任何移动操作,则应遵循三个规则。如果声明了移动操作,则“违反”三个规则不会有任何危害,因为生成由编译器生成的操作已变得非常严格。即使您未声明移动操作并违反了三个规则,如果用户声明了一个特殊函数并且由于某些特殊原因而自动生成了其他特殊函数,C ++ 0x编译器仍会向您发出警告。现在不推荐使用“ C ++ 03兼容性规则”。

我可以肯定地说,该规则变得不那么重要了。 C ++ 03中的真正问题是,实现不同的复制语义要求您用户声明所有相关的特殊功能,以便它们都不是编译器生成的(否则会做错事)。但是C ++ 0x更改了有关特殊成员函数生成的规则。如果用户仅声明这些函数之一来更改复制语义,则将阻止编译器自动生成其余的特殊函数。这很好,因为缺少声明会立即将运行时错误转换为编译错误(或至少是警告)。作为C ++ 03兼容性度量标准,仍然会生成一些操作,但是该操作已被弃用,并且至少应在C ++ 0x模式下产生警告。

由于有关编译器的限制性规则,生成特殊功能和C ++ 03兼容性,三个规则保持三个规则。

以下是一些适用于最新C ++ 0x规则的例子:

template<class T>
class unique_ptr
{
   T* ptr;
public:
   explicit unique_ptr(T* p=0) : ptr(p) {}
   ~unique_ptr();
   unique_ptr(unique_ptr&&);
   unique_ptr& operator=(unique_ptr&&);
};


在上面的示例中,不需要将任何其他特殊功能声明为已删除。由于限制性规则,它们根本不会生成。用户声明的移动操作的存在会禁用编译器生成的复制操作。但在这种情况下:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
};


现在,预期C ++ 0x编译器会发出有关编译器生成的复制操作可能执行错误操作的警告。在此,应遵守三件事的规则。在这种情况下,警告是完全适当的,它使用户有机会处理该错误。我们可以通过删除函数来解决此问题:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
   scoped_ptr(scoped_ptr const&) = delete;
   scoped_ptr& operator=(scoped_ptr const&) = delete;
};


因此,由于C ++ 03的兼容性,三个规则在这里仍然适用。 >

评论


实际上,N3126确实将unique_ptr的副本构造函数和副本赋值运算符定义为已删除-有人知道为什么吗?

– Philipp
2011年1月24日在20:49

@Philipp:限制性规则比N3126更新。但是,N3225仍将unique_ptr的复制操作声明为已删除。这不再是必须的,但这也没有错。因此,实际上没有必要更改unique_ptr的规范。

– sellibitze
2011-1-24 21:58



N3126的规则不太严格:如果存在用户声明的move构造函数,则不会隐式声明副本构造函数;如果存在用户声明的Move赋值运算符,则不会隐式声明副本赋值运算符。 unique_ptr同时具有用户声明的移动构造函数和移动赋值运算符,因此,即使应用N3126规则,我也不需要用户声明的副本构造函数和副本赋值运算符。并不是很重要,但是由于标准库类使用的约定可能被解释为最好的约定

– Philipp
11年1月24日在22:44

实践中,很高兴知道显式声明的副本构造函数和副本赋值运算符是否是有意的。

– Philipp
2011年1月24日22:45

#7 楼

我们不能说3的规则现在变成4的规则(或5),而不会破坏所有现有的强制3的规则并且不实现任何形式的移动语义的代码。

3的规则意味着如果您实施一项必须全部实施3。

也不知道会有任何自动生成的举动。 “ 3则规则”的目的是因为它们自动存在,并且如果您实现一个,则很可能另外两个的默认实现是错误的。

#8 楼

在一般情况下,是的,三个规则变成了五个规则,其中添加了move赋值运算符和move构造函数。但是,并非所有类都是可复制和可移动的,有些类只是可移动的,有些是可复制的。 br />

评论


我相信,即使一个类不可复制,您也要定义复制构造函数和赋值运算符(已删除)。因此,可移动资源管理类也应定义所有这五个类。

– Philipp
2011年1月24日14:23

@Philipp,我非常不同意,许多类不支持移动语义,仅出于某种美感就定义两个冗余函数是没有意义的。为什么std :: complex应该关心右值引用?

–莫蒂
2011年1月24日15:25



@Motti:为什么要定义常规副本语义?几乎所有可以复制的资源都可以移动。

–小狗
2011年1月24日15:33



@Motti:Philipp说应该将它们定义为已删除!因此,您应该明确反对他们不支持该操作的事实。

–康拉德·鲁道夫(Konrad Rudolph)
2011年1月24日15:36



@Konrad在我看来太冗长了,一旦定义了cctor,就不会定义mctor(据我了解当前草案)。您还会定义默认构造函数,将其删除为每个定义自定义构造函数的类吗?

–莫蒂
2011年1月24日15:51