概念
默认构造函数:无任何实参,执行默认初始化1。
- 如果存在类内的初始值,用它来初始化成员。
- 否则,默认初始化该成员。
合成的默认构造函数(Synthesized default constructor):编译器创建的构造函数1。
- 只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。
- 可以使用
= default
要求编译器自动生成默认构造函数。
构造函数与拷贝控制
如果一个类(基类或派生类)没有定义拷贝控制操作,则编译器将为它合成一个版本2。
合成的移动操作
如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或析构函数,编译器就不会为它合成移动构造函数和移动赋值运算符3。
如果一个类定义了析构函数,即使它通过
= default
的形式使用了合成的版本,编译器也不会为这个类合成移动操作4。
只有当一个类没有定义任何自己版本的拷贝控制成员,且它的所有数据成员都能移动构造或移动赋值时,编译器才会为它合成移动构造函数或移动赋值运算符5。
合成的构造函数何时被定义为删除的?
移动操作永远不会隐式地定义为删除的函数。
如果5我们显式地要求编译器生成
= default
6的移动操作,且编译器不能移动所有成员,则编译器会将移动操作定义为删除的函数。除了一个重要例外,什么时候将合成的移动操作定义为删除的函数遵循与定义删除的合成拷贝操作类似的原则7:
- 移动构造函数定义为删除的函数的条件是:有类成员定义了自己的拷贝构造函数且未定义移动构造函数,或者有类成员未定义自己的拷贝构造函数且编译器不能为其合成移动构造函数。移动赋值运算符的情况类似。
- 如果有类成员的移动构造函数或移动赋值运算符被定义为删除的或不可访问的,则类的移动构造函数或移动赋值运算符也被定义为删除的。
- 类似拷贝构造函数,如果类的析构函数被定义为删除的或不可访问的,则类的移动构造函数被定义为删除的。
- 类似拷贝赋值运算符,如果有类成员是 const 或是引用,则类的移动赋值运算符被定义为删除的。
测试
#include <limits.h>
#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class A {
int id_;
public:
A() { printf("A()\n"); }
// 如果有拷贝构造函数或拷贝赋值运算符,那么编译器不会自动合成移动构造函数
A(const A& rhs) : id_(rhs.id_) { printf("A(const A& %d)\n", id_); }
// A(A&& rhs) : id_(rhs.id_) { printf("A(A&& %d)\n", id_); }
A(int id) : id_(id) { printf("A(%d)\n", id_); }
~A() { printf("~A(%d)\n", id_); }
};
int main() {
// vector<unique_ptr<A>> vec;
// for (int i = 1; i <= 10; i++) {
// vec.push_back(unique_ptr<A>(new A(i)));
// }
vector<A> vec;
// TODO std::move 的机制是什么?
// 可以查阅《C++ Primer 第五版 中文版》
vec.push_back(std::move(A(10))); // 这里还是调用的复制构造函数
getchar();
return 0;
}
参考
《C++ Primer 中文版 第五版》7.1.4节(P235)、7.5节(P257)、15.7节(P551)、18.1.3节(P689)和第 13 章都介绍了关于构造函数的知识。