当我们定义一个类的时候。我们通常会显式或隐式的指定在此类的对象在创建、移动、销毁的时候要做些什么。
一个类通常通过定义五种特殊的成员函数来控制这些操作:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数。
如果我们没有定义这些成员函数,编译器会自动定义些缺失的操作。有时也会造成些意想不到的错误。
拷贝构造函数:
如果一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
class Foo{
public:
Foo(); //默认构造函数
Foo(const Foo&); //拷贝构造函数
//...
};
拷贝构造函数第一个参数必须是引用类型。(原因后面解释)
拷贝构造函数参数几乎总是一个const的引用。
拷贝构造函数会经常被隐式的使用,所以通常不应该声明成explicit。
合成拷贝构造函数
如果未定义拷贝构造函数,编译器会为我们定义一个。
即使定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。
一般情况,编译器从给定对象中一次将每个非static成员拷贝到正在创建的对象当中。
对类类型的成员,则调用他的合成拷贝函数。对于其他类型的成员,则直接拷贝。
拷贝初始化
我们可以从下面几个例子里看出初始化与拷贝初始化的不同。
string dots(10, '.'); //直接初始化
string s(dots); //直接初始化
string s2 = dots; //拷贝初始化
string null_book = "9-999-99999-9"; //拷贝初始化
string nines = string(100, '9'); //拷贝初始化
现在,只用了解拷贝初始化何时发生,以及拷贝初始是依靠拷贝构造函数或移动构造函数来完成的。
不仅在=定义变量时会发生,还会在下列情况发生:
·将一个对象作为实参传递给非引用类型的形参。
·从一个返回类型为非引用类型的函数返回一个对象。
·用花括号列表初始化数组元素或一个聚合类的成员。
当初始化标准库容器或调用其insert或push函数时,会使用拷贝初始化。
用emplace成员创建的元素都进行直接初始化。
参数和返回值
在函数调用过程中,具有非引用类型的参数要进行拷贝初始化。当一个函数具有非引用类型的返回类型时,返回值会被用来初始化调用方法的结果。
拷贝构造函数被用来初始化非引用类型的参数。所以拷贝构造函数的参数必须是引用类型。