拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
class Foo {
public:
Foo(); //默认构造函数
Foo(const Foo&); //拷贝构造函数
//...
};
拷贝构造函数的第一个参数必须是引用类型,虽然我们可以定义一个接受非const引用的拷贝构造函数,但此参数几乎总是一个const的引用。拷贝构造函数在几种情况下都会被隐式地使用。因此,拷贝构造函数通常不应该是explicit的。
合成拷贝构造函数
对某些类来说,合成拷贝构造函数用来阻止我们拷贝该类类型的对象,而一般情况,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中。
拷贝初始化
直接初始化与拷贝初始化的差异:
string dots(10,'.'); //直接初始化
string s(dots); //直接初始化
string s2=dots; //拷贝初始化
sttring null_book="9-999-99999-9"; //拷贝初始化
string nines=string(100,'9'); //拷贝初始化
当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数,当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。拷贝初始化通常使用拷贝构造函数来完成。
除了使用“=”定义变量时会发生拷贝初始化,还有以下情况:
1、将一个对象作为实参传递给一个非引用类型的形参
2、从一个返回类型为非引用类型的函数返回一个对象
3、用花括号列表初始化一个数组中的元素或一个聚合类的成员
拷贝初始化的限制
如果我们使用的初始化值要求通过一个explicit构造函数来进行类型转换,那么使用拷贝初始化还是直接初始化就不是无关紧要的了:
vector<int> v1(10); //正确:直接初始化
vector<int> v2=10; //错误:接受大小参数的构造函数是explicit的
void f(vector<int>); //f的参数进行拷贝初始化
f(10); //错误:不能用一个explicit的构造函数拷贝一个实参
f(vecotr<int> (10)); //正确:从一个int直接构造一个临时vector
编译器可以绕过拷贝构造函数
在拷贝初始化过程中,编译器可以跳过拷贝/移动构造函数,直接创建对象:
string null_book="9-999-99999-9" //拷贝初始化
改写为:
string null_book=("9-999-99999-9"); //编译器略过了拷贝构造函数
拷贝赋值运算符
与类控制其对象如何初始化一样,类也可以控制其对象如何赋值:
Sales_data trans,accum;
trans=accum; //使用Sales_data的拷贝赋值运算符
重载赋值运算符
重载运算符的本质上是函数,其名字由operator关键字后接表示要定义的运算符的符号组成。因此,赋值运算符就是一个名为operator=的函数。例如:
class Foo {
public:
Foo& operator=(const Foo&); //赋值运算符
//...
};
合成拷贝赋值运算符
与处理拷贝构造函数一样,如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符。合成拷贝运算符返回一个指向其左侧运算对象的引用。例如:
//等价于合成拷贝赋值运算符
Sales_data&
Sales_data::operator=(const Sales_data &rhs)
{
bookNo=rhs.bookNo; //调用string::operator=
units_sold=rhs.units_sold; //使用内置的int赋值
revenue=rhs.revenue; //使用内置的double赋值
return *this; //返回一个此对象的引用
}
析构函数
析构函数执行与构造函数相反的操作:构造函数初始化对象的非static成员,还可能做一些其他工作;析构函数释放对象使用的资源,并销毁对象的非static数据成员。
class Foo {
public:
~Foo(); //析构函数
//...
};
由于析构函数不接受参数,因此它不能被重载。
析构函数完成什么工作
在一个析构函数中,首先执行函数体,然后销毁成员,成员按初始化顺序的逆序销毁,在对象最后一次使用之后,析构函数的函数体可执行类设计者希望执行的任何收尾工作。在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。
什么时候会调用析构函数
无论何时一个对象被销毁,就会自动调用其析构函数,由于析构函数自动运行,我们的程序可以按需要分配资源,而无须担心何时释放这些资源。
合成析构函数
当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。对于某些类,合成析构函数被用来阻止该类型的对象被销毁,如果不是这种情况,合成析构函数的函数体就为空。
在(空)析构函数执行完毕后,成员会被自动销毁,析构函数自身并不直接销毁成员,成员是在析构函数体之后隐含的析构阶段被销毁的,在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分而进行的。