拷贝控制成员
C++中的拷贝控制成员包括:
- 构造函数
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数
- 移动赋值运算符
- 析构函数
构造函数
构造函数,又分为默认构造函数和自定义构造函数,其中默认构造函数包括:
- 系统自动合成(没有构造函数时)
- 无参构造函数
- 全部带有默认实参的构造函数
关于构造函数,这里重点介绍一下隐式类类型转换和显示的类类型转换
如果一个构造函数只含有一个参数例如int a,则该函数可以通过隐式转换将int类型的遍历转换为对象A。、
eg:
class A
{
public:
A(int a):a_(a){} //构造函数1
int a_;
int b_;
}
int a;
A t = a;//将调用构造函数1
void func(A t);
func(a);//将调用构造函数1
int b;
t=b;//将先调用构造函数1将b隐式转换为A的对象,然后调用A的拷贝赋值运算符
隐式类型转换发生的地方包括:
- 通过a初始化A对象时
- 通过a作为实参传入A的形参时
- 为A对象赋值为a时(此时先将a隐式转换为A对象,然后调用拷贝赋值运算符)
注意:隐式类型转换只允许一步到位,不允许a先转换为b,然后b再转换为A的对象
禁止隐式类型转换的方法,是在构造函数1的声明(只需要在类的声明后加explicit,类外定义不加)后加explicit关键字即可
但是这样还是可以通过static_cast<A>(a)等显示的强制类型转换符来进行a->A对象的强制类型转换
拷贝和赋值
A(const A& a);
A& operator=(const A& a);
为什么拷贝和赋值的形参是引用?
这里首先需要知道拷贝构造函数被调用的三个条件
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号初始化一个数组中的元素或者一个聚合类中的成员
- 拷贝/直接初始化一个类对象
所以很显然,如果不是引用,将会导致循环再次调用拷贝构造函数,从而出错
深拷贝和浅拷贝
- 深拷贝是指每个对象之间独立,拥有独立的内存空间
- 浅拷贝是指每个对象中有成员共享相同的内存空间
深拷贝和浅拷贝常见于对含有指针成员的类对象而言,如果是这样一般需要进行深拷贝,或者如果进行浅拷贝则需要在类中添加引用计数(或者通过智能指针来解决)
注意拷贝赋值运算符完成的是拷贝构造函数和析构函数的事
析构函数
当一个类对象结束其生命周期,则调用析构函数释放类资源
析构函数释放资源的操作发生在析构函数体执行完以后,这和构造函数恰好相反(构造函数是先初始化然后执行构造函数体)
拷贝控制成员的三/五法则
- 需要析构函数的类一定需要拷贝和赋值
- 需要拷贝/赋值的类一定需要赋值/拷贝
default和delete
default的使用方法是在类内函数声明或者类外函数定义处写出A()=defualt;即可(不可以两个都写)
delete的使用方法必须放在类内函数声明后面
default的作用是希望编译器为类生成合成的拷贝控制成员
delete的作用是阻止类生成某种拷贝控制成员
- 可以通过delete来阻止类的拷贝和赋值
- 析构函数不能定义为delete
- 如果析构函数是delete/某个类中有个类成员的析构函数是delete,则该类不能创建对象
- 可以通过动态分配的方式创建对象,但是不能delete 该指针
- 合成的拷贝控制成员也可能因为某些原因被定义为delete(P450)
也可以将拷贝控制成员声明成private,然后不定义即可,那么在类外使用时将编译报错(不能访问private成员),类内则将链接报错(因为没有定义该函数,所以链接报错)
移动和右值
这一块是C++11中引入的一个常考的点,分为三点来描述这里,分别是左值和右值 左值引用和右值引用 移动构造函数
左值和右值
左值:可以放在赋值符号左边的变量,即具有对应的可以由用户访问的存储单元,并且能够由用户去改变其值的量,左值就是存储在计算机内存的对象,而不是常量或者计算的结果,左值代表的是内存地址的值
右值:指的是临时性的对象的表达式,或者字面常量
- 左值可以取地址,右值不行
- 使用左值时,用的是对象的地址,而使用右值时则是使用的是存储在内存中某些地址中的数值
C++11中又将右值分为纯右值和将亡值
- 纯右值,指临时的对象或者字面常量
- 将亡值,指跟右值引用相关的表达式,这样的表达式通常是将要被移动的对象,例如返回右值引用T&&的函数返回值、std::move的返回值等
何时产生临时对象?
- 类型转换时
- 函数值传参时
- 表达式求值时
左值引用和右值引用
左值引用就是对左值进行引用的类型,右值引用就是对一个右值进行引用的类型,两者都属于引用类型,无论是声明一个左值引用还是右值引用,都必须立即进行初始化。左值引用是变量的别名
- 普通的左值引用不能绑定到右值
- 常量左值引用可以绑定到非常量左值,常量左值,右值,但是常量左值引用则只能读。
- 右值引用通常不能绑定到任何左值,但是可以通过std::move()函数将左值强制转化为右值
移动构造函数与移动拷贝运算符
所谓移动操作,实际上就是窃取资源,其不会分配任何资源,而是接管已经分配的内存(资源)
以类A为例,如果用a去初始化b,或者用a去赋值b,同时在初始化或者赋值后,b不再使用,则可以用移动构造函数/移动拷贝运算符,这两个函数的作用是用b去接管a中成员的内存空间,同时将a中的指针置空。
如果一个类定义了自己的拷贝控制成员(拷贝/移动/析构函数),那么编译器不会为该类合成两个移动函数,除非没有定义,所以如果没有移动构造函数,那么如果是一个右值那么也会调用拷贝构造函数,通过拷贝来构造
C++成员函数的引用限定符
&用于限定该函数返回的是一个左值
&&用于限定该函数返回的是一个右值
&/&&必须同时用于声明和定义后,且放在const后