拷贝控制c++primer13章

1、一个类通过定义五种特殊的成员函数来啊控制这些操作:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数

2、拷贝构造函数:如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。且通常不为explicit的,eg:

class foo{
public:
   foo();
   foo(const foo&);//拷贝构造函数
};

参数为引用的原因:若为非引用参数,则我们需要调用实参的拷贝来初始化一个对象,在调用实参的拷贝时我们又需要进行拷贝会陷入死循环。

3、合成拷贝构造函数:如果我们没有定义一个拷贝构造函数,即使我们定义了其他构造函数,编译器也会定义一个合成拷贝构造函数。
编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中。

4、每个成员的类型决定了其如何拷贝
类类型:使用其拷贝构造函数来拷贝;
内置类型:直接拷贝;
数组:逐一拷贝一个数组类型成员;
若一个数组元素是类类型:使用元素的拷贝构造函数来拷贝。

5、拷贝初始化不仅在用=时发生,下列情况也会:
将一个对象作为实参传递给一个非引用类型的形参;
从一个返回类型为非引用类型的函数返回一个对象;
用花括号列表初始化一个数据中的元素或一个聚合类中的成员;
某些类类型还会对它们所分配的对象使用拷贝初始化,eg:容器调用insert、push。

6、即使编译器可以绕过拷贝构造函数,但在拷贝点上,拷贝构造函数必须是存在并且可以访问的。

7、拷贝赋值运算符:一个名为operator=的函数,返回=左侧对象的引用。

8、合成拷贝赋值运算符:未定义时,编译器自行定义。

9、析构函数:析构函数释放对象使用的资源,并销毁对象的非static数据成员。在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。注意在一个析构函数中不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。成员销毁时发生什么完全依赖于成员的类型。

10、调用析构函数的时间
变量在离开其作用域时被销毁;
当一个对象被销毁时,其成员被销毁;
容器(无论是标准库容器还是数组)被销毁时,其元素被销毁;
对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁;
对于临时对象,当创建它的完整表达式结束时被销毁。

11、当指向一个对象的引用或指针离开作用域时,析构函数不执行。

12、三/五法则
需要析构函数的类也需要拷贝构造函数和拷贝赋值函数;
需要拷贝操作的类也需要赋值操作,反之亦然;
析构函数不能是删除的,对于定义了删除的析构函数的类,编译器将不允许定义该类型的变量或创建该类型的变量或临时对象;
如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的;
如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作;
新标准加入了移动构造函数因此对三/五法则更新如下:
所有五个拷贝控制成员应该看作一个整体:一般来说,如果一个类定义了任何一个拷贝操作,他就应该应以所有五个操作。

13、类的行为像一个值,意味着它应该也有自己的状态。当拷贝一个像值的对象时,副本和原对象是完全独立的。改变副本不会对原对象有任何影响,反之亦然。为了提供类值的行为,对于类管理的资源,每个对象都应该拥有一份自己的拷贝。

14、行为像指针的类共享状态。当拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。改变副本也会改变原对象,反之亦然。

15、定义行为像值的类时,对于拷贝赋值运算符来说,其一般结合了析构函数和拷贝构造函数的操作,所以我们要保证,即使将一个对象赋予其自身,我们也要保证能够安全运行,所以要注意析构和拷贝的顺序。因此,一个好的模式是,先将右侧运算对象拷贝到一个局部临时对象中,当拷贝完成后,销毁左侧运算对象。

16、令一个类展现类似指针的行为最好的办法是用sahred_ptr。但当我们想要直接管理资源时,我们应该引入引用计数,并将其保存在动态内存中。

17、引用计数的工作方式:
(1)除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录有多少对象与正在创建的对象共享状态。当我们创建一个对象时,只有一个对象共享状态,因此将引用计数初始化为1。
(2)拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定对象的状态又被一个新用户所共享。
(3)析构函数递减计数器,指出共享状态的用户又少了一个。如果计数器变为0,则析构函数释放状态。
(4)拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0,意味着它的共享状态没有用户了,拷贝赋值运算符就必须销毁状态。

19、swap函数在类中比较微妙,对于内置类型,由于内置类型没有自己定义的swap,因此会调用std::swap,但对于一个类的成员有自己特定的swap函数,调用std::swap是错误的,尽管编译会通过。eg:

//假定我们有一个Foo类,他有一个类型为HasPtr的成员h,HasPtr定义了自己的swap
//我们采用如下操作就是错误的
void swap(Foo &lhs, Foo &rhs){
     std::swap(lhs.h, rhs.h);
}
//我们希望调用HasPtr的swap,正确方案如下:
void swap(Foo &lhs, Foo &rhs){
     using std::swap;  //此处尽管显示的用use声明,但没有隐藏HasPtr版本swap的声明
     swap(lhs.h, rhs.h);
}

20、定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了叫做(copy and swap)的操作。这种技术将左侧运算对象与右侧运算对象的一个副本进行了交换。

//注:HasPtr为一个自己定义的类似shared_ptr的类
HasPtr& Hasptr::operator=(HasPtr rhs){
//将一个对象以传值的方式传递给了赋值运算符(有可能是拷贝也有可能是移动构造)
       swap(*this, rhs); //rhs 现在指向本对象曾经食用的内存
       return *this;   //rhs被销毁,从而delete了rhs中的指针。
}

上例中的赋值运算符从底层效率来看并不理想:
个人理解为:即使他可以处理左值或者右值引用,但也需要等实参的类型确定了才能决定具体采用哪种做法,即是否需要对实参进行拷贝,可以进一步分为两种情况即将参数定义为const &或者&&这样就可以直接精准匹配。

21、右值引用:右值引用只能绑定到一个将要销毁的对象。(也解释了unique_ptr的特性)右值引用可以绑定到要求转换的表达式、字面常量、返回右值的表达式。左值引用正好相反。

返回左值引用的函数,联通复制、下标、解引用和前置递增/减都是返回左值的表达式。

返回非引用类型的函数,连同算术、关系、位以及后置递增/减都生成右值。我们可以将一个const&或者&&绑定到这类表达式上。

因为右值引用只能绑定在临时对象所以我们可知,该引用的对象将要被销毁,并且该对象没有其他用户,也就意味着,右值引用可以自由地接管所引用的对象的资源。也叫做“窃取”状态。

22、移动构造函数参数为右值引用。并且还需要保证移动后对象处于一个可以安全被销毁的状态。一旦资源移动完成,原对象不再继续指向该资源。该过程不分配任何新的内存,这点与拷贝构造函数不同,也大大提升了效率。

23、我们需要一个移动操作不抛出异常,是因为两个互相关联的事实:首先,虽然移动操作通常不抛出异常,但抛出异常也是允许的。其次,标准库容器能对异常发生时其自身行为提供保障。例如我们定义了一个vector的移动版本的push_back,假设当我们在需要分配新的空间时,将旧空间内的元素移动到新的空间时发生了异常,注意此时有部分移动过去的元素在旧空间中已经不存在,有部分元素还未移动,此使我们无法保证vector的稳定性。因此我们要事先声明不会又异常发生即:noexcept。

24、建议不要随便使用移动操作:由于一个移后源对象具有不确定状态,对其调用std::move是危险的,当我们调用move时,必须确认移后源对象没有其他用户。通过在类代码中小心地使用move,可以大幅度提升新能。

25、就标准中,我们允许向字符串s1,s2的连接结果赋值,新标准为了兼容仍然允许此方法,但我们为了斌面这样的操作,在参数列表后放置一个引用限定符。eg:

//
s1 + s2 = "wow";
//
Foo &operator=(const Foo&) &;//只能向可更改的左值赋值

引用限定符可以为&或者&&分别表示this指向的是一个左值还是右值,只能用于非static成员函数。可以与const一起用即:

Foo someMem() const &;

26、可以用引用限定符来区分重载版本(与const一样)。可以综合引用限定符和const来区分一个成员函数的重载版本。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值