【C++primer阅读记录】拷贝构造函数和析构函数
拷贝构造函数
在函数调用中,具有非引用类型的参数和具有非引用的返回类型时,返回值会被用来初始化调用方。其实就是因为这两种情况都是pass by value,会调用拷贝构造函数。
这也解释了为什么拷贝构造函数需要使用引用类型参数,如果是非引用类型参数,那么调用了这个构造函数就会使用pass by value,我们就必须 拷贝这个实参,为了拷贝这个实参我们又需要拷贝构造函数。
explicit关键字只对一个实参的构造函数有效,让这个构造函数只能用于直接初始化而不能用于隐式转换过程。也就是不能隐形地使用该构造函数。
vector<int> v1(10); //构造包含10个int元素的vector,此构造函数是explicit的,无法用于隐式转换过程。
vector<int> v2 = 10; //错误,实际过程猜测是会想使用10这个参数传递到构造函数中,然后建立临时对象再调用拷贝构造函数赋予给v2。但是由于接受10这个参数的构造函数是explicit的,因此调用失败。
重载运算符
重载运算符本质上是函数,器名字由operator关键字后接表示要定义的运算符号组成。因此,赋值运算符就是一个名为operator=的函数。
重载运算符的参数是运算符的运算对象。如果一个运算符是成员函数,那左侧运算对象就绑定到隐式的this参数,右侧运算对象作为显示参数传递。
class Foo
{
public:
Foo& operator=(const Foo&); //赋值运算符,返回一个返指向其左侧运算对象的引用。
//实验中把Foo&返回类型改为void仍可以运行。
}
拷贝赋值运算符
类通过拷贝赋值运算符控制对象如何赋值。如果不定义拷贝赋值运算符(实际上是函数),编译器就会生成一个合成拷贝赋值运算符(synthesized copy-assignement oprator)。对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值(??没懂。)。一般情况下会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,然而这一工作其实也是通过成员类型的拷贝赋值运算符完成的。
析构函数
析构函数的执行逻辑
析构函数执行与构造函数相反的操作:构造函数初始化对象的非static数据成员,析构函数释对象使用的资源并销毁对象的非static数据成员。
由于析构函数不接受参数,也无返回类型,因此不能被重载。
构造函数有初始化部分和函数体(function body),析构函数有一个函数体和析构部分。在构造函数中,成员的初始化是在函数体执行之前完成的,按照他们在类中出现的顺序进行初始化。在析构函数中,先执行函数体,再销毁成员。
析构函数怎么时候会被执行?
只要一个对象被销毁的时候,就会自动调用其析构函数:
变量离开其作用域时被销毁 (比如函数中的局部变量)
当一个对象被销毁时,成员被销毁(也就是类成员的成员的析构函数)
容器被销毁时,其元素被销毁
对于动态分配的对象,当指向它的指针应用delete运算符时被销毁
对于临时对象,当创建它的完整表达式结束时被销毁
感觉主要就分类两类:一类是由于变量离开了作用域(生存空间)被自动销毁的时候调用析构函数,一类就是我们自己分配了空间之后进行销毁操作调用析构函数(比如delete)。
{
Sales_data *p = new Sales_data; //p内置指针指向默认初始化后的Sales_data
auto p2 = make_shared<Sales_data>(); //p2为一个shared_ptr
Sales_data item(*p); //使用p指向的Sales_data对象初始化item
vector<Sales_data> vec; //局部vec对象
vec.push_back(*p2) //拷贝p2指向的对象到vec中
delete p; //调用p指向对象的析构函数
}//p2被销毁,调用其析构函数,使得shared_ptr引用次数减少,若为0则释放对象空间。
//item被销毁,调用Sales_data的析构函数
//vec被销毁,调用vector的析构函数。
合成析构函数
当一个类未定义自己的析构函数时,编译器就会定义一个合成析构函数。
class A
{
public:
~A() {}
}
相当于合成析构函数,在函数体被执行后,成员就会自动销毁。就会运行类成员的析构函数。析构函数本身并不直接销毁成员,成员是在隐含的析构阶段中被销毁的。销毁会调用析构函数,销毁内置类型的时候不会有特殊动作。
需要析构函数的类也需要拷贝和赋值操作
合成析构函数不会delete一个指针数据成员,因此需要定义一个析构函数来释放构造函数分配的内存。
class A
{
public:
~A(){delete ps;}
private:
int *ptri;
}
如果使用默认的拷贝赋值或者拷贝初始化操作,当
A a;
A b = a;
变量a和b用于合成函数简单拷贝指针成员,因此两个变量a、b的整形指针都会指向同一片内存空间。当这两个变量同时离开作用域被销毁的时候,就会出现同一片内存空间被delete两次。
基本原则:如果一个类需要构造拷贝函数,几乎肯定它也需要一个拷贝赋值运算符。反之亦然。
定义删除的函数
在新标准下,我们可以通过将拷贝构造函数和拷贝赋值函数定义为删除的函数来阻止拷贝。
删除的函数:虽然我们声明了它们,但不能以任何方式使用它们。
struct NoCopy
{
NoCopy() = default; //
NoCopy(const NoCopy&) = delete; //阻止拷贝。必须在第一次声明的时候使用。
NoCopy &operator=(const NoCopy&) = delete; //阻止赋值
}
对于析构函数以删除的类型,不能定义该类型的变量或释放指向该类型动态分配对象的指针。
析构函数如果是删除的,不可访问的(private),或者是类里的成员具有这样的析构函数,那类的合成构造拷贝函数是删除的。
如果类内有const、引用成员,则合成的拷贝赋值运算被定义为删除的
本质上这些规则的含义是:如果一个类有数据成员不能默认构造、拷贝、赋值或销,则对应的成员函数将被定义为删除的。

浙公网安备 33010602011771号