在c++ 11中,三法则变成了五法则?

所以,在观察了对右值引用的这堂课很精彩之后,我认为每个类都将受益于这样一个“移动构造函数”,template<class T> MyClass(T&& other) < em >编辑< / em >,当然还有一个“移动赋值操作符”,template<class T> MyClass& operator=(T&& other)如Philipp在他的回答中指出的那样,如果它有动态分配的成员,或通常存储指针。就像你应该有一个copy-ctor,赋值操作符和析构函数,如果前面提到的点适用。 想法吗?< / p >
77093 次浏览

是的,我认为为这样的类提供一个移动构造函数会很好,但请记住:

  • 这只是一个优化。

    只实现一个或两个复制构造函数、赋值操作符或析构函数可能会导致错误,而没有移动构造函数只会潜在地降低性能。

  • 如果没有修改,移动构造函数不能总是应用。

    有些类总是分配它们的指针,因此这些类总是在析构函数中删除它们的指针。在这些情况下,你需要添加额外的检查,说他们的指针是否已分配或已移动(现在是空的)。

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

3规则的意思是,如果你实现了一个,你就必须实现所有3个。

也不知道会有任何自动生成的移动。“规则3”的目的是因为它们自动存在,如果你实现了其中一个,那么其他两个的默认实现很可能是错误的。

我想说,三原则变成了三、四、五原则:

每个类应该明确定义一个 以下集合的特殊成员 功能:< / p >
  • 没有一个
  • 析构函数,复制构造函数,复制赋值操作符

此外,每个显式定义析构函数的类都可以显式定义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在自动生成复制构造函数和复制赋值操作符方面的行为不对称,但要安全得多。因此,定义move构造函数和move赋值操作符的可能性非常有用,并创建了新的可能性(纯可移动类),但坚持c++ 03三大规则的类仍然是可以的。

对于资源管理类,如果底层资源不能复制,则可以将复制构造函数和复制赋值操作符定义为deleted(这算作定义)。通常你还是需要move构造函数和move赋值操作符。复制和移动赋值操作符将经常使用swap实现,就像在c++ 03中一样。谈论swap;如果我们已经有了移动构造函数和移动赋值操作符,专业std::swap将变成不重要的,因为泛型std::swap在可用的情况下使用了移动构造函数和移动赋值操作符(这应该足够快)。

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

在一般情况下,是的,三的规则变成了五的规则,添加了move赋值操作符和move构造函数。然而,不是所有类是可复制和可移动的,有些只是可移动的,有些只是可复制的。

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

  1. 拷贝构造函数
  2. 赋值运算符
  3. 析构函数

然而,省略move构造函数或move赋值操作符并不意味着存在错误。它五月是一个错过的优化机会(在大多数情况下)或移动语义与这个类不相关,但这不是一个bug。

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

基本上,它是这样的:如果你没有声明任何移动操作,你应该尊重规则三。如果声明一个move操作,“违反”3规则也没有什么害处,因为编译器生成的操作的生成已经变得非常受限。即使你没有声明move操作,并且违反了规则三,c++ 0x编译器也会给你一个警告,如果一个特殊函数是用户声明的,而其他特殊函数由于“c++ 03兼容性规则”而自动生成。

我认为可以肯定地说,这条规则变得不那么重要了。c++ 03中真正的问题是,实现不同的复制语义需要用户声明与所有相关的特殊函数,这样它们就不会是编译器生成的(否则会做错误的事情)。但是c++ 0x改变了关于特殊成员函数生成的规则。如果用户只声明了其中一个函数来改变复制语义,则会阻止编译器自动生成其余的特殊函数。这很好,因为缺失的声明现在会将运行时错误转换为编译错误(或者至少是警告)。作为c++ 03兼容性度量,仍然会生成一些操作,但该生成被认为已弃用,至少应该在c++ 0x模式下产生警告。

由于编译器生成的特殊函数的限制规则和c++ 03的兼容性,3的规则仍然是3的规则。

下面是一些适用于最新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的兼容性,三原则在这里仍然适用。

我不敢相信没有人链接到

基本上这篇文章主张的是“零规则”。 我不适合引用整篇文章,但我相信这是主要观点:

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

.构造函数或复制/移动赋值操作符

在我看来,这一点很重要:

通用的“包中的所有权”类包含在标准中 库:std::unique_ptrstd::shared_ptr。通过使用 自定义删除器对象,两者都已变得足够灵活,可以进行管理

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

根据c++ 11标准(见附件D的[depr.impldec]):

如果类具有用户声明的复制赋值操作符或用户声明的析构函数,则不建议使用复制构造函数的隐式声明。如果类具有用户声明的复制构造函数或用户声明的析构函数,则不建议使用复制赋值操作符的隐式声明。

2013年,EWG投票反对在c++ 2014中实现这一提议。对该提案做出决定的主要理由与对破坏现有代码的普遍担忧有关。

最近,提出了再次对c++ 11的措辞进行了调整,以实现非正式的五规则,即

如果这些函数是用户提供的,则编译器不能生成复制函数、移动函数或析构函数。

如果被EWG批准,这个“规则”很可能被c++ 17采用。

简单来说,记住这一点。

0法则:

Classes have neither custom destructors, copy/move constructors or copy/move assignment operators.

< p > 3原则: 如果你实现了其中任何一个的自定义版本,你就实现了所有这些

Destructor, Copy constructor, copy assignment

< p > 5原则: 如果你实现了一个自定义的move构造函数或move赋值操作符,你需要定义所有5个。需要移动语义。

Destructor, Copy constructor, copy assignment, move constructor, move assignment

< p > 四点半法则: 与第5条规则相同,但使用了复制和交换习惯用语。通过包含swap方法,copy赋值和move赋值合并为一个赋值操作符

Destructor, Copy constructor, move constructor, assignment, swap (the half part)

< >强引用< / >强:

https://www.linkedin.com/learning/c-plus-plus-advanced-topics/rule-of-five?u=67551194 https://en.cppreference.com/w/cpp/language/rule_of_three < / p >