我们对一个对象的操作一般分为下面四种
- 创建
- 拷贝
- 移动
- 销毁
创建
创建这个操作也就是我们最熟悉的创建一个对象
假设有一个class A:
class A{
public:
A(int i);
A() = default;
private:
int a_i;
};
我们为它定义了两种构造函数:
- 含参数的
- 默认构造函数
因为第一种构造函数只含有一个实参,因为它定义了转换为此类型的隐式转换机制,这种构造函数叫做
转换构造函数
:
A a = 1;
我们可以使用
explicit
关键字来阻止这种隐式转换:
class A{
public:
explicit A(int i);
A() = default;
private:
int a_i;
};
explicit 构造函数只能用于直接初始化,也即是 A a = A(0);
拷贝
拷贝构造函数:
构造函数的第一个参数是自身类型的引用,且任何额外的参数都有默认值:
A(const A &a){
}
const可加可不加
默认的拷贝构造函数是将其参数的成员逐个拷贝到正在创建的对象中,每个成员的类型决定了它如今拷贝:对于类类型的成员,使用它的拷贝构造函数,对于内置类型的成员则直接拷贝
拷贝初始化发生的情况:
- 将一个对象作为实惨传递给一个非引用类型的形参
- 用 = 定义变量时
A s = a;
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的一个元素或一个聚合类中的成员
- 当初始化标准库容器或调用其insert/push成员时
用emplace成员创建的元素都进行直接初始化
拷贝赋值运算符
A& operator=(const A&); //赋值运算符
当这样使用时会调用拷贝赋值:
A c,d;
c = d;
赋值运算符通常应该返回一个指向左侧运算符对象的引用
默认的拷贝赋值就是将右侧对象的成员拷贝赋值到左侧对象
移动
右值引用 &&:必须绑定到右值的引用,只能绑定到一个将要销毁的对象
左值表达式表示的是一个对象的身份,右值表达式表示的是对象的值
右值引用可以绑定到要求转换的表达式,字面常量或返回右值的表达式,这些左值引用是不可以的。
右值引用不能直接绑定到一个左值上去。
int i = 24;
int &r = i;//r引用i
int && rr = i;//错误,右值不能绑定左值
int &r2 = i*42;//错误,i*42是一个右值
const int &r3 = i*42;//正确,const的引用可以绑定到右值
int &&rr2 = i *42;//绑定到乘法结果上
移动构造函数:
A(A&& a)
当完成资源的移动后,移动构造函数还需要确认移后源对象出于:销毁它是无害的状态。特别是,一旦完成移动,源对象必须不再指向被移动的资源。移动构造函数会让它直接接管源对象的资源。
移动赋值运算符
A &operator=(A &&a);
如果一个类定于了自己的拷贝构造函数,拷贝赋值运算符或者析构函数,编译器就不会为它合成移动构造函数或移动赋值函数了。如果一个类没有移动操作,就会用相应的拷贝操作来替代
只有当一个累没有定义任何自己的拷贝控制成员,且每个非static数据成员都可以移动时,编译器才会为它合成移动构造函数或者移动赋值运算符