一. 特殊成员函数ios
(一)概述编程
1. 特殊成员函数指C++会自行生成的成员函数,主要有6种:默认构造函数、析构函数、复制构造函数、复制赋值函数、移动构造函数和移动赋值函数。函数
2. 默认生成的特殊成员函数都具备public访问权限且是inline的非虚函数(除析构例外)。一般,若是这些函数不被相关代码使用,编译器不会为其产生真正的函数代码。this
3. 若是基类析构函数是虚函数,则编译器为派生类生成的析构函数也是个虚函数。spa
4. 默认的复制或移动都是指对非静态成员的操做,它们都是“按成员复制”或“按成员移动”的。而“按成员移动”实际上更像是按成员移动的请求,由于有些类型不具有移动操做,对于这些对象会经过其复制操做来实现“移动”。日志
(二)C++11中的生成机制code
1. 默认构造函数:仅当类中不包含用户声明的构造函数时(含无参/带参构造函数、复制构造和移动构造)才生成。对象
2. 析构函数:仅当基类的析构函数为virtual时,派生类的析构函数才是虚的。其与C++98的机制基本相同。惟一区别在于析构函数默认为noexcept。blog
3. 复制构造函数:生成条件(① 该类未声明复制构造;②该类未声明移动操做;③该类未声明复制赋值或析构函数)。注意,在当前声明复制赋值或析构函数时,仍会生成默认的复制构造函数,但该行为会被逐渐废弃。资源
4. 复制赋值函数:生成条件(① 该类未声明复制赋值;②该类未声明移动操做;③该类未声明复制构造或析构函数)。注意,在当前声明复制构造或析构函数时,仍会生成默认的复制赋值函数,但该行为会被逐渐废弃。
5. 移动构造和移动赋值函数:仅当类中不包含用户声明的复制操做、移动操做和析构函数等3个条件知足时才生成。
2、理解生成机制
(一)生成机制背后的思想
1. 复制操做是彼此独立的,即声明了其中一个,并不会阻止编译器生成另外一个。
早期编译器认为默认的复制构造和复制赋值都是按成员复制的。所以,它仍愿意提供这种最简单的“浅拷贝”方式以供调用(如遇到成员变量是引用或常量时,就能够有机会提示报错信息)。而若是须要进行“深拷贝”则须由用户自行定义相应的函数。
2. 移动操做并不彼此独立,即声明了其中一个,就会阻止编译器生成另外一个。
假设若是声明一个移动构造函数,实际上表示移动操做与编译器生成的按默认“按成员移动”是不一样的。而若是“按成员移动”操做不符合要求时,按成员进行的移动赋值也极及可能不符合要求。
3. 复制操做与移动操做会相互影响
①若是声明复制操做(不管是复制构造或复制赋值)的行为,这代表对象按成员复制不符合要求。编译器认为,既然按成员复制不符合要求,那么“按成员移动”也很可能不符合要求。
②反之,若是声明了移动操做(移动构造或移动赋值),就表示“按成员移动”不符合要求,也就没理由指望按成员复制符合要求。
(二)大三律及推论
1. 大三律(Rule of Three):若是声明了复制构造、复制赋值或析构函数中的任何一个,就得同时声明全部这三个。
①在一种复制操做中进行资源管理,也极有可能在另外一种复制操做中也须要进行。
②该类的析构函数也会参与到该资源的管理中(释放之)。
2. 推论
①若是声明的析构函数,则平凡的按成员复制不适于该类。即复制操做不会被自动生成,由于它们的行为都是不正确的。但在C++11中仍保留生成复制操做函数,仅仅是出于兼容C++98的遗留代码而己,这种行为将逐渐被废弃。
②若是声明了析构函数,就不会生成移动操做(由于析构函数会抑制复制操做,而复制操做又会阻止生成移动操做)。
(三)注意事项
1. 为了消除因相互抑制而产生的依赖关系,在C++11中能够经过“=default”来显式指示编译器生成默认版本的特殊成员函数。
2. 成员函数模板在任何状况下都不会抑制特殊成员函数的生成。
3. 在己经显式声明析构函数时,会阻止生成移动操做,而但仍会生成复制操做(将逐渐被废弃)。这可能会产生反作用,即原来移动操做可能会变成复制操做。
4. 根据大三律原则,当声明析构函数时,通常应同时提供复制操做和移动操做函数。
【编程实验】特殊成员函数生成机制
#include <iostream> #include <map> using namespace std; //1. 演示特殊成员函数之间的抑制关系 //1.1 自定义析构函数会阻止默认移动操做(含移动构造和移动赋值) class Foo { private: int i; public: Foo() = default; Foo(const Foo& f) = delete; //删除复制构造函数!让移动操做只会去找移动操做的函数,而不是被复制取代! Foo& operator=(const Foo&) = delete; //理由同上! ~Foo() {}; //这里自定义析构函数! }; //1.2 自定义析构函数使“移动操做”变为“复制操做”! class StringTable { std::map<int, std::string> values; void makeLogEntry(std::string s){}; //日志记录 public: StringTable() { makeLogEntry("Creating StringTable Object"); } ~StringTable() //注意,这里自定义了析构函数,会产生反作用! { makeLogEntry("Destroying StringTable Object"); } }; //2. 大三律 class Bar { public: virtual ~Bar() = default; //自定义了析构函数,则应同时提供复制和移动操做! Bar(Bar&&) = default; //提供移动操做的支持 Bar& operator=(Bar&&) = default; Bar(const Bar&) = default;//提供移动操做的支持 Bar& operator=(const Bar&) = default; }; //3. 特种成员函数模板 class Widget { public: template<typename T> Widget(const T& rhs) { cout <<"Widget(const T& rhs)" << endl; } template<typename T> Widget& operator=(const T& rhs) { cout << "Widget& operator=(const T& rhs)" << endl; return *this; } public: Widget() = default; }; int main() { //1. 自定义析构函数,会阻止移动操做 Foo f; //Foo f1 = std::move(f); //编译失败! 因为自定义了析构函数,默认移动操做被删除。 //f1 = std::move(w); //编译失败! 缘由同上。 StringTable st1; StringTable st2 = std::move(st1); //这里本意是调用移动构造函数,但因该类的析构函数阻止生成 //默认的移动操做,转而变为复制操做。而对于values对象的复 //制,而td::map<int, std::string>的复制每每比移动慢不少! //3. 特种成员函数模板不会抑制编译器生成默认的特殊成员函数 Widget w; Widget w1 = w; //调用编译器生成的默认复制构造函数 Widget w2 = 2; //调用函数模板:Widget(const T& rhs) w1 = w2; //调用编译器生成的默认复制赋值函数 w1 = 2; //调用函数模板:Widget& operator=(const T& rhs) return 0; } /*输出结果 Widget(const T& rhs) Widget& operator=(const T& rhs) */