Effective C++ 条款06 若不想使用编译器自动生成的函数,就该明确拒绝

      在设计并不是很复杂的类中使用编译器提供的那些函数也能很好地完成工作,但是对于一些复杂的类中使用这类函数会导致很多问题(资源泄露,浅拷贝等等都会导致许多你并不希望的问题出现),所以一般来说若是我们不需要编译器提供的这些函数就该明确告诉它,可能下意识你会说既然不需要干脆就不要声明这些函数,但是编译器会在你未声明的前提下默认替你产出一份。而如果你自己声明则又违背了自己一开始对类不允许拷贝的设计初衷。所以解决方法有哪些呢?

①  将方法声明为private

     一般来说我们可供用户使用的函数都是public的,而如果你将拷贝赋值操作符声明为private则自然就阻止了拷贝

    缺点:成员函数以及友元函数依旧可以访问拷贝赋值操作符,除非你不去定义它,那么如果某些人不慎调用了那么连接器会报错。

    注:”将成员函数声明为private并且故意不实现它 ”这一伎俩是如此为大家接受,因此被用在C++ iostream程序库中阻止拷贝行为。是的看看你手中的标准程序库实现码中的ios_base,basic_ios,和sentry。你会发现无论哪一个,其拷贝构造函数和拷贝赋值运算符都被声明为private,而且都没有定义。

 ②  让你的类继承一个不允许拷贝的基类

 class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale :private Uncopyable
{
//阻止拷贝的三种方式:
//①将拷贝运算符或拷贝构造函数放在private中
//②定义一个不可拷贝的父类然后继承它
//③C++11标准中提出可以将函数定义为delete以阻止拷贝
//设计了一个为了阻止拷贝的基类然后继承它 因为子类拷贝需要调用基类的拷贝运算符,由于基类的拷贝运算符无法访问
//所以子类也阻止拷贝 
};         

  优点:这个与方法①相比成功解决了友元与成员函数依然能拷贝的问题,因为只要任何人——甚至是成员函数与友元函数——尝试拷贝HomeForSale对象,编译器便试着生成一个拷贝构造函数和一个拷贝赋值操作符,而这些函数的”编译器生成版 ”会尝试调用其基类的对应函数,那些调用会被编译器拒绝,因为其基类的拷贝函数是private的。

  缺点:但由于这个类总是扮演的是基类的角色,因此使用这项技术可能导致多重继承(因为你往往还需要继承其他类)

    ③将函数声明为delete(C++11特性)

  删除的函数是这样一种函数:我们虽然声明了它们,但不能以任何方式使用它们。在函数的参数列表后面加上=delete来指出我们希望将它定义为删除的:

struct NoCopy
{
NoCopy() = default;//使用合成的默认构造函数
NoCopy(const NoCopy&) = delete;//阻止拷贝
NoCopy& operator=(const NoCopy&) = delete;//阻止赋值
~NoCopy() = default;//使用合成析构函数
};

=delete通知编译器(以及我们代码的读者),我们不希望定义这些成员。

      与=default不同,=delete必须出现在函数第一次声明的时候,这个差异与这些声明的含义在逻辑是吻合的。一个默认的成员值影响为这个成员而生成的代码,因此=default知道编译器生成代码时才需要。而另一方面,编译器需要知道一个函数是删除的,以便禁止试图使用它的操作。

      与=default的另一个不同之处是,我们可以对任何函数指定=delete(我们只能对编译器可以合成的默认构造函数或拷贝控制成员使用=default)。虽然删除函数的主要用途是禁止拷贝控制成员,但当我们希望引导函数匹配过程时,删除函数有时也是有用的。

析构函数不能是删除的成员

     值得注意的是,如果析构函数被删除。就无法销毁此类型的对象了。对于一个删除了析构函数的类型,编译器将不允许定义该类型的变量或创建该类的临时对象,因为如果一个成员的析构函数是删除的,则该成员无法被销毁。而如果一个成员无法被销毁,则对象整体也就无法销毁了。

     对于删除了析构函数的类型,虽然我们不能定义这种类型的变量或成员,但可以动态分配这种类型的对象。但是不能释放这些对象:

struct NoDtor
{
NoDtor() = default;//使用合成默认构造函数
~NoDtor() = delete;//我们不能销毁NoDtor类型的对象
};
NoDtor nd;//错误:NoDtor的析构函数是删除的
NoDtor *p = new NoDtor();//我们无法delete p
delete p;//错误:NoDtor的析构函数是删除的

合成的拷贝控制成员可能是删除的

     如前所述,如果我们未定义控制成员,编译器会为我们定义合成的版本。类似的,如果一个类未定义构造函数,编译器会为其合成一个默认构造函数。对某些类来说,编译器将这些合成的成员定义为删除的函数:

    1. 如果类的某个成员的析构函数是删除的或不可访问的(例如是 private的),则类的合成析构函数被定义为删除的。

    2. 如果类的某个成员的拷贝构造函数是删除的或不可访问的,则类的合成拷贝构造函数被定义为删除的。如果类的某个成员的析构函数是删除的或不可访问的,则类合成的拷贝构造函数也被定义为删除的。

    3. 如果类的某个成员的拷贝赋值运算符是删除的或不可访问的,或是类有一个const成员或是引用成员,则类的合成拷贝赋值运算符被定义为删除的。

     4. 如果类的某个成员的析构函数是删除的或不可访问的,或是类有一个引用成员,它没有类内初始化器,或是类有一个const成员,它没有类内初始化器且其类型未显式定义默认构造函数,则该类的默认构造函数被定义为删除的。

      本质上,这些规则的含义是:如果一个类有数据成员不能默认构造,拷贝,复制或销毁,则对应的成员函数将被定义为删除的。

      一个成员有删除的或不可访问的析构函数会导致合成的默认和拷贝构造函数被定义为删除的,这看起来可能有些奇怪,其原因是,如果没有这条规则,我们可能会创建出无法销毁的对象。

      对于具有引用成员或无法默认构造的const成员的类,编译器不会为其合成默认构造函数,这应该不奇怪。同样不出人意料的规则是:如果一个类有const成员,则它不能使用合成的拷贝赋值运算符。毕竟,此运算符试图赋值所有成员,而讲一个新值赋予一个const对象时不可能的。

      虽然我们可以将一个新值赋予一个引用成员,但这样做改变的是引用所指的对象的值,而不是引用本身。如果为这样的类合成拷贝赋值运算符,则赋值后,左侧运算对象仍然指向与赋值前一样的对象,而不会与右侧运算对象指向相同的对象。由于这种行为看起来并不是我们所期望的,因此对于有引用成员的类,合成拷贝赋值运算符被定义为删除的。

--------------------------------------------------------------------------------------------------------------------------------- 合成拷贝控制与继承

      基类或派生类的合成拷贝成员的行为与其他合成的构造函数、赋值运算符或析构函数类似:它们对类本身的成员依次进行初始化、赋值或销毁的操作。此外,这些合成的成员还负责使用基类中对应的操作对一个对象的直接基类部分进行初始化、赋值或销毁的操作。例如,


      

  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值