一个类有5种特殊的成员函数:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数。如果没有定义这些拷贝控制成员,编译器会自动为它定义缺失的操作。
A a;
A b = a;//报错
13.1 拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,而且任何额外参数都有默认值(也就是说最少只需一个实参),则此构造函数是拷贝构造函数。
class Foo
{
public:
Foo();
Foo(const Foo&);
//...
};
第一个参数必须是引用类型。此参数几乎总是一个const的引用。
拷贝构造函数通常不应该是explicit的,因为在几种情况下都会被隐式地使用。
如果拷贝构造函数是explicit的,那么
A a;
A b = a;//报错
就会报错
即使我们定义了其他构造函数(不是拷贝构造函数),编译器也会为我们定义一个合成的拷贝构造函数。
合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。(除了static成员)
如果类成员是A类类型,则会调用A类的拷贝构造函数来拷贝成员类。内置成员直接拷贝。数组逐个拷贝。
C++的一大误区——深入解释直接初始化与复制初始化的别
http://blog.csdn.net/ljianhui/article/details/9245661
string dots(10, '.'); //直接初始化
string s(dots); //直接初始化
string s2 = dots; //拷贝初始化
string null_book = "9-999-99999-9"; //拷贝初始化
string nines = string(100, '9'); //拷贝初始化
class Foo
{
public:
int x;
/*1*/
Foo(){cout << 1 << endl;};
/*2*/
Foo(const int xx){x = xx;cout << 2 << endl;}
/*3*/
Foo(const Foo&){cout << 3 << endl;};
/*4*/
Foo& operator=(const Foo&){cout << 4 << endl;}
};
int main()
{
Foo f1(0); /*2*/
Foo f2 = 0; /*2和3,但是编译器把3优化掉了所以看不到,假如把2设为explicit,或者把3设为private:,都会报错,说明有一个先用2后3的带类型转化的过程*/
Foo f3 = f1; /*3*/
Foo f4(f1); /*3*/
Foo f5 = Foo(); /*1和3,但是编译器把3优化掉了所以看不到*/
Foo f6; /*1*/
f6 = f1; /*4*/
return 0;
}
直接初始化时,我们要求编译器使用普通的函数匹配,来选择参数最匹配的构造函数。使用构造初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中(还可能要类型转换)。
拷贝初始化首先使用指定构造函数创建一个临时对象,然后后用拷贝构造函数将那个临时对象拷贝到正在创建的对象。
传递非引用实参,函数返回非引用类型,花括号列表初始化数组或聚合类时都用到拷贝构造函数。
复制构造函数 不等于 operator=(), 后者是赋值运算符。
拷贝初始化首先使用构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。
拷贝构造函数被用来初始化非引用类类型参数,这一特性揭示了为什么拷贝构造函数自己的参数必须是引用类型,如果其参数不是引用类型,则调用永远不会成功(参考不完全类型)。为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们有需要调用拷贝构造函数,如此无限循环(递归)。
vector<int> v1(10); //正确:直接初始化
vector<int> v2 = 10;//错误,接受大小参数的vector构造函数是explicit的
void f(vector<int> v);
f(10); //错误,原因同上
f(vector<int>(10)); //正确,拷贝构造函数
编译器可以略过拷贝构造函数。(不是必须)。
string null_book = "9-999-99999-9";//拷贝初始化
<