提问人:Xeo 提问时间:1/24/2011 最后编辑:FlexoXeo 更新时间:11/23/2021 访问量:78913
三法则变成五法则与C++11?[已结束]
Rule-of-Three becomes Rule-of-Five with C++11? [closed]
问:
因此,在观看了这个关于右值引用的精彩讲座后,我认为每个类都会受益于这样的“移动构造函数”、编辑,当然还有“移动赋值运算符”,正如 Philipp 在他的回答中指出的那样,如果它有动态分配的成员,或者通常存储指针。就像你应该有一个复制者、赋值运算符和析构函数一样,如果前面提到的几点适用的话。
思潮?template<class T> MyClass(T&& other)
template<class T> MyClass& operator=(T&& other)
答:
是的,我认为为此类类提供移动构造函数会很好,但请记住:
这只是一种优化。
仅实现一个或两个复制构造函数、赋值运算符或析构函数可能会导致错误,而没有移动构造函数只会降低性能。
如果不进行修改,则不能始终应用 Move 构造函数。
某些类总是分配它们的指针,因此这些类总是在析构函数中删除它们的指针。在这些情况下,您需要添加额外的检查,以说明其指针是已分配还是已移走(现在为 null)。
评论
unique_ptr
unique_ptr
auto_ptr
unique_ptr
boost::noncopyable
some classes have always their pointers allocated...
在这种情况下,移动通常以交换的形式实现。同样简单快捷。(实际上更快,因为它将释放移动到右值的析构函数)
我们不能说 3 规则现在变成了 4 规则(或 5),而不破坏所有执行 3 规则并且不实现任何形式的移动语义的现有代码。
3 法则意味着如果你实现一个,你必须实现所有 3 个。
也不知道会有任何自动生成的动作。“3 法则”的目的是因为它们自动存在,如果你实现一个,那么其他两个的默认实现很可能是错误的。
我会说三法则变成了三、四和五法则:
每个类都应该明确地定义一个 以下一组特殊成员 功能:
- 没有
- 析构函数、复制构造函数、复制赋值运算符
此外,显式定义析构函数的每个类都可以显式定义移动构造函数和/或移动赋值运算符。
通常,以下一组特殊成员之一 函数是明智的:
- 无(对于许多隐式生成的特殊成员函数正确且快速的简单类)
- 析构函数、复制构造函数、复制赋值运算符(在本例中为 类将不可移动)
- 析构函数、移动构造函数、移动赋值运算符(在这种情况下,该类将不可复制,对于基础资源不可复制的资源管理类很有用)
- 析构函数、复制构造函数、复制赋值运算符、移动构造函数(由于复制省略,如果复制赋值运算符按值获取其参数,则不会产生开销)
- 析构函数、复制构造函数、复制赋值运算符、移动构造函数、 移动赋值运算符
注意:
- 不会为显式声明任何其他特殊成员函数(如析构函数、复制构造函数或移动赋值运算符)的类生成该移动构造函数和移动赋值运算符。
- 不会为显式声明移动构造函数或移动赋值运算符的类生成该复制构造函数和复制赋值运算符。
- 并且,具有显式声明的析构函数和隐式定义的复制构造函数或隐式定义的复制赋值运算符的类被视为已弃用。
特别是,以下完全有效的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
};
有点烦人,但可能比替代方案更好(在这种情况下,自动生成仅用于复制的特殊成员函数,没有移动的可能性)。
与三巨头规则相比,不遵守规则可能会造成严重损害,不显式声明移动构造函数和移动赋值运算符通常没问题,但在效率方面往往次优。如上所述,仅当没有显式声明的复制构造函数、复制赋值运算符或析构函数时,才会生成移动构造函数和移动赋值运算符。这与传统的 C++03 行为在自动生成复制构造函数和复制赋值运算符方面不对称,但更安全。因此,定义移动构造函数和移动赋值运算符的可能性非常有用,并创造了新的可能性(纯粹可移动的类),但是遵循三巨头的C++03规则的类仍然可以。
对于资源管理类,如果无法复制基础资源,则可以将复制构造函数和复制赋值运算符定义为已删除(计为定义)。通常,您仍然需要移动构造函数和移动赋值运算符。复制和移动赋值运算符通常使用 实现,如在 C++03 中。谈论;如果我们已经有了 move-constructor 和 move-assignment 运算符,那么专门化 std::swap
将变得不重要,因为泛型使用 move-constructor 和 move-assignment 运算符(如果可用)(这应该足够快)。swap
swap
std::swap
不用于资源管理(即没有非空析构函数)或子类型多态性(即没有虚拟析构函数)的类应声明五个特殊成员函数中的任何一个;它们都将自动生成,并且行为正确且快速。
评论
struct C { virtual ~C() = default; };
在一般情况下,是的,三的规则变成了五的规则,并添加了移动赋值运算符和移动构造函数。但是,并非所有类都是可复制和可移动的,有些只是可移动的,有些只是可复制的。
评论
std::complex
我不这么认为,三法则是一种经验法则,它指出实现以下其中一项但不是全部的类可能是有问题的。
- Copy 构造函数
- 赋值运算符
- 破坏者
但是,省略移动构造函数或移动赋值运算符并不意味着存在 bug。这可能是在优化时错失了机会(在大多数情况下),或者移动语义与此类无关,但这不是一个错误。
虽然最佳做法是在相关时定义移动构造函数,但这不是强制性的。在许多情况下,移动构造函数与类无关(例如),所有在 C++03 中行为正常的类将继续在 C++0x 中行为正常,即使它们没有定义移动构造函数。std::complex
基本上,它是这样的:如果你不声明任何移动操作,你应该遵守三法则。如果声明移动操作,“违反”三规则并没有什么坏处,因为编译器生成的操作的生成已经变得非常严格。即使您不声明移动操作并违反三法则,如果用户声明了一个特殊函数,并且由于现已弃用的“C++03 兼容性规则”而自动生成了其他特殊函数,则 C++ 编译器也应该向您发出警告。
我认为可以肯定地说,这条规则变得不那么重要了。C++03 中真正的问题是,实现不同的复制语义需要用户声明所有相关的特殊函数,以便它们都不是编译器生成的(否则会做错误的事情)。但是 C++0 更改了有关特殊成员函数生成的规则。如果用户仅声明其中一个函数来更改复制语义,则会阻止编译器自动生成其余的特殊函数。这很好,因为缺少声明会立即将运行时错误转换为编译错误(或至少是警告)。作为 C++03 兼容性度量,仍会生成某些操作,但此生成被视为已弃用,至少应在 C++0x 模式下生成警告。
由于关于编译器生成的特殊函数和 C++03 兼容性的规则相当严格,因此三法则仍然是三法则。
以下是一些应该适用于最新 C++0 规则的示例:
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++0 编译器应该会生成一个警告,说明编译器生成的复制操作可能会做错事。在这里,三事规则应该得到尊重。在这种情况下,警告是完全合适的,并让用户有机会处理错误。我们可以通过删除的功能来摆脱这个问题:
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 兼容性。
评论
unique_ptr
unique_ptr
我不敢相信没有人与此有关。
基本上,文章主张“零规则”。 我引用整篇文章是不合适的,但我相信这是重点:
具有自定义析构函数、复制/移动构造函数或复制/移动赋值运算符的类应专门处理所有权。 其他类不应有自定义析构函数,复制/移动 构造函数或复制/移动赋值运算符。
恕我直言,这一点也很重要:
常见的“包内所有权”类包含在标准中 library: 和 .通过使用 自定义删除器对象,两者都已变得足够灵活,可以进行管理 几乎任何类型的资源。
std::unique_ptr
std::shared_ptr
以下是自 11 年 1 月 24 日以来的当前状态和相关发展的简短更新。
根据 C++11 标准(参见附录 D 的 [depr.impldec]):
如果类具有用户声明的复制赋值运算符或用户声明的析构函数,则不推荐使用复制构造函数的隐式声明。如果类具有用户声明的复制构造函数或用户声明的析构函数,则不推荐使用复制赋值运算符的隐式声明。
实际上,有人提议取消已弃用的行为,使C++14成为真正的“五法则”,而不是传统的“三法则”。2013年,EWG投票反对这项将在C++ 2014中实施的提案。对该提案做出决定的主要理由与对破坏现有代码的普遍担忧有关。
最近,再次有人提议调整C++11的措辞,以实现非正式的五条规则,即
如果这些函数中的任何一个是用户提供的,则不会由编译器生成任何复制函数、移动函数或析构函数。
如果得到EWG的批准,C++17可能会采用该“规则”。
评论
简单来说,只要记住这一点。
0 法则:
Classes have neither custom destructors, copy/move constructors or copy/move assignment operators.
第3条规则: 如果实现其中任何一个的自定义版本,则将实现所有这些版本。
Destructor, Copy constructor, copy assignment
第5条规则: 如果实现自定义移动构造函数或移动赋值运算符,则需要定义所有 5 个构造函数。移动语义需要。
Destructor, Copy constructor, copy assignment, move constructor, move assignment
四分半法则: 与 5 法则相同,但带有复制和交换成语。加入交换方法后,复制分配和移动分配将合并到一个分配运算符中。
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
评论