Effective C++——条款6(第2章)

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

Explicitly disallow the use of compiler-generated functions you do not want.

在某些情况下,希望保持对象的唯一性,不想让对象有其他副本.如下:

class HomeForSale { ... };
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1);        // 企图拷贝h1,不应该通过编译
h1 = h2;                   // 企图拷贝h2,不应该通过编译

阻止这一类代码的编译并不是很直观.通常如果不希望 class 支持某一个特定机能,只要不声明对应的函数就可以了.但这个策略对copy构造函数和copy assignment操作符却起不到作用.因为条款5已经指出,如果不声明它们,而某些人尝试调用它们,编译器会声明它们.

这就进入了一个困境.如果不声明copy构造函数和copy assignment操作符,编译器可能产出一份.于是 class 就支持copying.如果声明它们,class 还是支持copying,而目标却是要阻止copying(保持对象的唯一性).

答案的关键是,所有编译器产出的函数都是 public.为阻止这些函数被创建出来,需要自行声明它们,但这里并没有什么需求使必须将它们声明为 public.因此可以将copy构造函数和copy assignment操作符声明为 private.借由明确地声明一个成员函数,可以阻止编译器暗自创建其专属版本;而令这些函数为 private,得以成功阻止其他人调用它.

一般而言这个做法并不绝对安全,因为member函数和 friend 函数还是可以调用 private 函数.除非不定义它们,那么如果某些人不慎调用任何一个,就会获得一个连接错误."将成员函数声明为private并且故意不实现它们"这一伎俩广为人接受,因而被用在C++iostream程序库中阻止copying行为.如果看看标准程序库实现码中的ios_base,base_ios和sentry.会发现无论哪一个,其copy构造函数和copy
assignment操作符都被声明为 private 且没有定义.

将这个伎俩施行于HomeForSale也很简单:

class HomeForSale {
public:
    ...
private:
    HomeForSale(const HomeForSale &);            // 只有声明
    HomeForSale& operator=(const HomeForSale&);
};

有了上述定义,当客户企图拷贝HomeForSale对象,编译器会阻止它.如果不慎在member函数或 friend 函数内那么做,连接器则会阻止.

 将连接期错误移动至编译器是可能的(而且是好事,毕竟越早侦测出错误越好),只要将copy构造函数和copy assignment操作符声明为 private 就可办到,但不是在HomeForSale自身,而是在一个专门为了阻止copying动作而设计的base class 内.这个base class 非常简单:

class Uncopyable {
protected:                                // 允许derived对象构造和析构
    Uncopyable() {}
    ~Uncopyable() {}
private:
    Uncopyable(const Uncopyable&);        // 阻止copying
    Uncopyable* operator=(const Uncopyable&);
};
    为了阻止HomeForSale对象被拷贝,唯一需要做的就是继承Uncopyable:
class HomeForSale : private Uncopyable {    // class不再需要声明copy构造函数
};

这是行得通的,因为只要任何人——甚至是member函数或 friend 函数——尝试拷贝HomeForSale对象,编译器便试着生成一个copy构造函数和一个copy assignment操作符,而正如条款12所述,这些函数的"编译器生成版"会尝试调用其base class 的对应兄弟,那么调用会被编译器拒绝,因为其base class 的拷贝函数是 private.

Uncopyable class 的实现和运用颇为微妙,包括不一定得以 public 继承它,以及Uncopyable的析构函数不一定得是 virtual 等等.Uncopyable不含数据,因此符合条款39所述的empty base class optimization资格.但由于它总是扮演base class,因此这项技术可能导致多重继承(因为往往还需要继承其他 class),而多重继承有时会阻止empty base class optimization.通常可以忽略这些微妙点.

注意:

为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为 private 并且不予实现.使用像Uncopyable这样的base class 也是一种做法.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-07 11:40:12

Effective C++——条款6(第2章)的相关文章

Effective C++——条款13(第3章)

第3章    资源管理 Resource Management 所谓资源就是,一旦用了它,将来必须还给系统.C++程序中最常使用的资源就是动态内存分配(如果分配内存从来都增归还,会导致内存泄露).其他常见的资源还有文件描述符(file descriptors),互斥锁(mutex locks),图形界面中的字型和笔刷,数据库连接,以及网络sockets.不论哪一种资源,重要的是,不再使用它时,必须将它还给系统. 条款13:    以对象管理资源 Use objects to manage res

Effective C++——条款5(第2章)

第2章    构造/析构/赋值运算 Constructors,Destructors,and Assignment Operator 几乎每一个 class 都会有一个或多个构造函数,一个析构函数,一个copy assignment 操作符. 条款05:    了解C++默默编写并调用哪些函数 Know what functions C++ silently writes and calls 什么时候empty class 不再是个empty class 呢?当C++处理过它之后.如果自己没有声

Effective C++——条款15(第3章)

条款15:    在资源管理类中提供对原始资源的访问 Provide access to raw resources in resources-managing classes 资源管理类(resource-managing classes)很棒.它们是对抗资源泄露的堡垒.在一个良好的环境中将依赖这样的classes来处理和资源之间的所有互动.而不是直接处理原始资源,但这个环境并不完美,许多API直接涉及资源,因此有时只能绕过资源管理对象直接访问原始资源(raw resources). 例如,条

Effective C++——条款14(第3章)

条款14:    在资源管理类中小心copying行为 Think carefully about copying behavior in resource-managing classes 条款13导入这样的观念:"资源取得时机便是初始化时机"(Resource Acquisition Is Initializaiton,RAII),并以此作为"资源管理类"的脊柱,也描述了auto_ptr和tr1::shared_ptr如何将这个观念表现在heap-based资源

Effective C++——条款9(第2章)

条款09:    绝不在构造和析构过程中调用 virtual 函数 Never call virtual functions during construction or destruction 不应该在构造函数和析构函数期间调用 virtual 函数,因为这样的调用不会带来预想的结果. 假设有个 class 继承体系,用来模塑股市交易如买进,卖出的订单等等.这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中也需要创建一笔适当记录.下面是一个看起来颇为合理的做法: class Tr

Effective C++——条款7(第2章)

条款07:    为多态基类声明 virtual 析构函数 Declare destructors virtual in polymorphic base classes 设计以下时间基类TimeKeeper: class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); }; class AtomicClock : public TimeKeeper { ... }; class WaterClock : public TimeKeeper {

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

effective c++ 条款4 make sure that objects are initialized before they are used

1 c++ 类的数据成员的初始化发生在构造函数前 class InitialData { public: int data1; int data2; InitialData(int a, int b) { data1 = a: //this is assignment data2 = b; //this is assignment } /* InitialData(int a, int b):data1(a),data2(b) //this is initial {} */ } 2 不同cpp文

More Effective C++ 条款34 如何在一个程序中结合C++和C

1. C++和C混合使用的前提之一就是编译器产生兼容的目标文件(.lib和.dll等).所谓"兼容",指的是编译器在"预编译器相依的特性上"一致,如int和double大小,参数压栈机制等,只有在这个基础上才能讨论结合使用C++和C模块的问题. 2. 在1的基础上,要结合使用C++和C的模块,主要有以下几点需要注意: 1). name mangling(名称重整) Name mangling是C++用于支持函数重载的机制,它对重载的函数名称进行一定改变,使得每个函数