Effective Modern C++ 条款 17 编译器自动生成/合成的特种成员函数总结
本文承接上文,总结自《Effective Modern C++》条款 17:理解特种成员函数的生成机制
特种成员函数
- C++98 四种:默认构造、析构、复制构造、复制赋值运算符
- C++11增加两种:移动构造、移动赋值运算符
合成机制
-
只有用到了才合成
-
没有显式声明任何构造的时候才会合成默认构造
-
合成的都是 public、inline 的
-
合成的都是非虚函数,除非该类的基类析构是虚函数
-
C++11 的析构默认是 noexcept 的
-
两种复制操作彼此独立,声明一个,不影响另一个(为兼容遗留代码,目前生成声明一个复制操作,依然会合成另一个,但最好用
=default
显式声明,因为后续标准可能会改变该行为) -
两种移动操作彼此关联,声明一个,不会自动合成另一个
-
一旦声明了复制操作,就不会合成移动操作;反之亦然,一旦声明移动操作,就废除复制操作
-
Rule of Three 指导原则:如果声明了复制构造、复制赋值、析构中的任何一个,就得同时声明所有 3 个
- 推论:如果声明了析构,则不该合成复制操作(为兼容遗留代码,不影响合成复制操作,但已废弃,后续标准可能改变)
- C++11 规定只要声明了析构,就不会合成移动
-
合成的移动操作实际上是“按成员移动”请求。如果成员不支持移动操作,则执行复制操作
-
建议:使用
= default
清晰表达意图,避免错误- 例如原本仅声明默认构造的程序,新增加一个析构(打印 log),将导致禁用合成移动操作,功能测试仍可通过,但是本可以执行移动操作的现在只能复制,慢若干数量级
-
成员函数模版在任何情况下都不抑制特种成员函数合成(P111)
总结
- 只有用到了才合成,默认都是 public、inline、非虚(特例:如果父类析构为虚,合成的子类析构为虚)
- 默认构造:类中不含用户声明的构造时才合成
- 析构:基类析构为虚才为虚,C++11 中默认为 noexcept
- 复制构造:按成员进行非静态成员复制构造。如果声明了移动操作,则删除复制构造。已有复制赋值或析构,仍然可以合成复制构造,但标记为 deprecated
- 复制赋值:按成员进行非静态成员的复制赋值。如果声明了移动操作,则删除复制赋值。已有复制构造或析构,仍然可以合成复制赋值,但标记为 deprecated
- 移动构造和移动赋值:按成员进行非静态成员移动。限制条件多,仅在类中不含显式复制、移动、析构声明时才合成,声明一个移动操作也会抑制另一个。移动操作也会移动其基类部分
测试代码
#include <iostream>
using namespace std;
class Foo {
public:
Foo() = default;
Foo(Foo&& f) { cout << "moving Foo" << endl; };
Foo(const Foo& f) { cout << "copying Foo" << endl; };
};
class Bar {
public:
// virtual ~Bar() = default;
// Bar& operator=(const Bar& b) = delete;
private:
Foo f;
};
int main() {
Bar a;
Bar b(move(a));
}
直接运行上述代码输出 moving Foo
,打开任意一个注释,输出 copying Foo
。说明 =delete
和 =default
都会被编译器认为显式声明。
扩展阅读
本文作者:Zijian/TENG(微信公众号:好记性如烂笔头),转载请注明原文链接:https://www.cnblogs.com/tengzijian/p/16209694.html