拷贝初始化
拷贝初始化发生的条件与情景:
@使用“=”定义变量的时候
@将一个对象作为实参传递给一个非引用类型的行参
@从一个返回类型为非引用类型的函数返回一个对象
@从花括号列表初始化一个数组中的元素或一个聚合类中的成员。
拷贝初始化的限制
如果我们使用的初始化值要求通过一个explicit 的构造函数来进行类型转换。如果我们希望使用从函数返回一个值,我们不能隐式使用一个explicit 构造函数。编译器可以绕过拷贝构造函数,但是这个拷贝构造函数必须存在并且可以访问。
析构函数:
构造函数初始化对象的非static 数据成员,析构函数释放对象使用的资源,并销毁对象的非static 数据成员。
析构函数调用场景:
变量在离开其作用域时被销毁
当一个对象被销毁时,其成员被销毁
容器被销毁,元素被销毁
对与动态分配的对象,当对它的指针引用应用delete 运算符的时候被销毁
对与临时对象,当创建它的完整表达式结束时被销毁
注意:当指向一个对象的引用或指针离开作用域时,析构函数不会执行。
析构函数执行语义
析构函数体本身并不直接销毁成员,成员是在析构函数体之后隐含的析构阶段中被销毁的,在整个对象销毁过程中,析构函数体做为成员销毁步骤之外的另一个部分进行的。
三/五法则
合成的拷贝构造函数,与赋值构造函数,意味着在赋值的时候拷贝指针成员,意味着多个对象可能指向同一个块内存。
如果一个函数需要析构函数,那么我们几乎可以肯定他也必须有一个拷贝构造函数和赋值构造函数。
需要拷贝操作的类也需要赋值操作符,反之如此。
=default
我们只能对具有合成版本的成员函数使用=default;
=delete
删除对象函数,即阻止拷贝.
以上两者的区别:
1.编译时机不同
2.后者对任何函数使用
特别的:如果一个析构函数被设置了后者,我们只能用NEW ,但是空间不会被释放。
赋值运算符
当编写赋值运算符时,需要记住两点:
如果将一个对象赋给它自身,赋值运算必须能正常工作。
大多数符赋值运算符组合了析构函数和拷贝构造函数的工作。
一个好的方法是在销毁左侧运算对象资源之前拷贝右侧运算对象。
拷贝控制 和 资源管理
拷贝的语义其实有两种:
一种是拷贝的行为像指针,就是拷贝对象与源对象来自同一个指针指向的事务,所以这段底层的数据是共享的。
一种是拷贝的行为像值,就是完全复制一份数据到新的地址,这样同样的一片数据就变成了两个独立的副本。
我们一般的值行为拷贝就是通过拷贝运算符这种东西来实现的。
其中有以下两个规则我们需要知道:
如果一个对象赋予它自身,复制运算符必须能正常工作.
大多数复制运算符组合了析构函数和拷贝构造函数的工作
至于行为像指针的类,我们可以通过做一个引用计数来实现
对象移动
新标准最新的东西是增加了对象移动的功能,因为一个拷贝操作需要重新分配一次内存,虽然使用alloctor 内存分配类来分配这个内存,但是终究效率上要欠缺一些。
对象移动就是,先给目标对象分配一个内存,然后直接逐项的复制信息到我们的目标对象中。
注意:IO类和unique_ptr 类可以移动但是不支持拷贝。
右值引用
这就是为了移动拷贝新加进标准的引用类型。我们可以通过&&来绑定一个右值,使用这个右值来初始化我们的对象。
左值持久,右值短暂
首先,右值只能绑定临时对象,我们可以得出以下结论
1 所引用的对象将要被销毁
2 该对象没有其它用户
3 右值引用的对象可以自由的接管所引用的对象。
4 注意,变量是左值
根据以上,不论我们是使用移动构造函数还是移动赋值运算符
原则上是:
移动源对象必须可析构
用户自己定义的构造函数优先级最高
必须具有析构函数,才可以考虑使用移动构造函数
移动右值,拷贝左值
当没有移动构造函数的时候,直接拷贝构造
也可以拷贝并交换复制,直接执行移动操作。