分享
 
 
 

More Effective C++ 条款26(下)

王朝c/c++·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

条款26:限制某个类所能产生的对象数量(下)

允许对象来去自由

我们知道如何设计只允许建立一个实例的类,我们知道跟踪特定类的对象数量的工作是复杂的,因为在三种不同的环境中都可能调用对象的构造函数,我们知道消除对象计数中混乱现象的方法是把构造函数声明为private。还有最后一点值得我们注意:使用thePrinter函数封装对单个对象的访问,以便把Printer对象的数量限制为一个,这样做的同时也会让我们在每一次运行程序时只能使用一个Printer对象。导致我们不能这样编写代码:

建立 Printer 对象 p1;

使用 p1;

释放 p1;

建立Printer对象p2;

使用 p2;

释放 p2;

...

这种设计在同一时间里没有实例化多个Printer对象,而是在程序的不同部分使用了不同的Printer对象。不允许这样编写有些不合理。毕竟我们没有违反只能存在一个printer的约束。就没有办法使它合法化么?

当然有。我们必须把先前使用的对象计数的代码与刚才看到的伪构造函数代码合并在一起:

class Printer {

public:

class TooManyObjects{};

// 伪构造函数

static Printer * makePrinter();

~Printer();

void submitJob(const PrintJob& job);

void reset();

void performSelfTest();

...

private:

static size_t numObjects;

Printer();

Printer(const Printer& rhs); //我们不定义这个函数

}; //因为不允许

//进行拷贝

// (参见Effective C++条款27)

// Obligatory definition of class static

size_t Printer::numObjects = 0;

Printer::Printer()

{

if (numObjects >= 1) {

throw TooManyObjects();

}

继续运行正常的构造函数;

++numObjects;

}

Printer * Printer::makePrinter()

{ return new Printer; }

当需要的对象过多时,会抛出异常,如果你认为这种方式给你的感觉是unreasonably harsh,你可以让伪构造函数返回一个空指针。当然客户端在使用之前应该进行检测。

除了客户端必须调用伪构造函数,而不是真正的构造函数之外,它们使用Printer类就象使用其他类一样:

Printer p1; // 错误! 缺省构造函数是

// private

Printer *p2 =

Printer::makePrinter(); // 正确, 间接调用

// 缺省构造函数

Printer p3 = *p2; // 错误! 拷贝构造函数是

// private

p2->performSelfTest(); // 所有其它的函数都可以

p2->reset(); // 正常调用

...

delete p2; // 避免内存泄漏,如果

// p2 是一个 auto_ptr,

// 就不需要这步。

这种技术很容易推广到限制对象为任何数量上。我们只需把hard-wired常量值1改为根据某个类而确定的数量,然后消除拷贝对象的约束。例如,下面这个经过修改的Printer类的代码实现,最多允许10个Printer对象存在:

class Printer {

public:

class TooManyObjects{};

// 伪构造函数

static Printer * makePrinter();

static Printer * makePrinter(const Printer& rhs);

...

private:

static size_t numObjects;

static const size_t maxObjects = 10; // 见下面解释

Printer();

Printer(const Printer& rhs);

};

// Obligatory definitions of class statics

size_t Printer::numObjects = 0;

const size_t Printer::maxObjects;

Printer::Printer()

{

if (numObjects >= maxObjects) {

throw TooManyObjects();

}

...

}

Printer::Printer(const Printer& rhs)

{

if (numObjects >= maxObjects) {

throw TooManyObjects();

}

...

}

Printer * Printer::makePrinter()

{ return new Printer; }

Printer * Printer::makePrinter(const Printer& rhs)

{ return new Printer(rhs); }

如果你的编译器不能编译上述类中Printer::maxObjects的声明,这丝毫也不奇怪。特别是应该做好准备,编译器不能编译把10做为初值赋给这个变量这条语句。给static const成员(例如int, char, enum等等)确定初值的功能是最近才加入到C++中的,所以一些编译器还不允许这样编写。如果没有及时更新你的编译器,可以把maxObjects声明为在一个private内匿名枚举类型里的枚举元素,

class Printer {

private:

enum { maxObjects = 10 }; // 在类中,

... // maxObjects为常量10

};

或者象non-const static成员一样初始化static常量:

class Printer {

private:

static const size_t maxObjects; // 没有赋给初值

...

};

// 放在一个代码实现的文件中

const size_t Printer::maxObjects = 10;

后面这种方法与原来的方法有一样的效果,但是显示地确定初值能让其他程序员更容易理解。当你的编译器支持在类定义中给const static成员赋初值的功能时,你应该尽可能地利用这个功能。

一个具有对象计数功能的基类

把初始化静态成员撇在一边不说,上述的方法使用起来就像咒语一样灵验,但是另一方面它也有些繁琐。如果我们有大量像Printer需要限制实例数量的类,就必须一遍又一遍地编写一样的代码,每个类编写一次。这将会使大脑变得麻木。应该有一种方法能够自动处理这些事情。难道没有方法把实例计数的思想封装在一个类里吗?

我们很容易地能够编写一个具有实例计数功能的基类,然后让像Printer这样的类从该基类继承,而且我们能做得更好。我们使用一种方法封装全部的计数功能,不但封装维护实例计数器的函数,而且也封装实例计数器本身。(当我们在条款29中测试引用计数时,将看到需要同样的技术。有关这种设计的测试细节,参见我的文章counting objects)

Printer类的计数器是静态变量numObjects,我们应该把变量放入实例计数类中。然而也需要确保每个进行实例计数的类都有一个相互隔离的计数器。使用计数类模板可以自动生成适当数量的计数器,因为我们能让计数器成为从模板中生成的类的静态成员:

template<class BeingCounted>

class Counted {

public:

class TooManyObjects{}; // 用来抛出异常

static int objectCount() { return numObjects; }

protected:

Counted();

Counted(const Counted& rhs);

~Counted() { --numObjects; }

private:

static int numObjects;

static const size_t maxObjects;

void init(); // 避免构造函数的

}; // 代码重复

template<class BeingCounted>

Counted<BeingCounted>::Counted()

{ init(); }

template<class BeingCounted>

Counted<BeingCounted>::Counted(const Counted<BeingCounted>&)

{ init(); }

template<class BeingCounted>

void Counted<BeingCounted>::init()

{

if (numObjects >= maxObjects) throw TooManyObjects();

++numObjects;

}

从这个模板生成的类仅仅能被做为基类使用,因此构造函数和析构函数被声明为protected。注意private成员函数init用来避免两个Counted构造函数的语句重复。

现在我们能修改Printer类,这样使用Counted模板:

class Printer: private Counted<Printer> {

public:

// 伪构造函数

static Printer * makePrinter();

static Printer * makePrinter(const Printer& rhs);

~Printer();

void submitJob(const PrintJob& job);

void reset();

void performSelfTest();

...

using Counted<Printer>::objectCount; // 参见下面解释

using Counted<Printer>::TooManyObjects; // 参见下面解释

private:

Printer();

Printer(const Printer& rhs);

};

Printer使用了Counter模板来跟踪存在多少Printer对象,坦率地说,除了Printer的编写者,没有人关心这个事实。它的实现细节最好是private,这就是为什么这里使用private继承的原因(参见Effective C++条款42)。另一种方法是在Printer和counted<Printer>之间使用public继承,但是我们必须给Counted类一个虚拟析构函数。(否则如果有人通过Counted<Printer>*指针删除一个Printer对象,我们就有导致对象行为不正确的风险——参见Effective C++条款14。)条款24已经说得很明白了,在Counted中存在虚函数,几乎肯定影响从Counted继承下来的对象的大小和布局。我们不想引入这些额外的负担,所以使用private继承来避免这些负担。

Counted所做的大部分工作对于Printer的客户端来说都是隐藏的,但是这些客户端可能很想知道有当前多少Printer对象存在。Counted模板提供了objectCount函数,用来提供这种信息,但是因为我们使用private继承,这个函数在Printer类中成为了private。为了恢复该函数的public访问权,我们使用using声明:

class Printer: private Counted<Printer> {

public:

...

using Counted<Printer>::objectCount; // 让这个函数对于Printer

//是public

...

};

这样做是合乎语法规则的,但是如果你的编译器不支持命名空间,编译器就不允许这样做。如果这样的话,你应使用老式的访问权声明语法:

class Printer: private Counted<Printer> {

public:

...

Counted<Printer>::objectCount; // 让objectCount

// 在Printer中是public

...

};

这种更传统的语法与uning声明具有相同的含义。但是我们不赞成这样做。TooManyObjects类应该也应用同样的方式来处理,因为Printer的客户端如果要捕获这种异常类型,它们必须有能力访问TooManyObjects。.

当Printer继承Counted<Printer>时,它可以忘记有关对象计数的事情。编写Printer类时根本不用考虑对象计数,就好像有其他人会为它计数一样。Printer的构造函数可以是这样的:

Printer::Printer()

{

进行正常的构造函数运行

}

这里有趣的不是你所见到的东西,而是你看不到的东西。不检测对象的数量就好像限制将被超过,执行完构造函数后也不增加存在对象的数目。所有这些现在都由Counted<Printer>的构造函数来处理,因为Counted<Printer>是Printer的基类,我们知道Counted<Printer>的构造函数总在Printer的前面被调用。如果建立过多的对象,Counted<Printer>的构造函数就会抛出异常,甚至都没有调用Printer的构造函数。

最后还有一点需要注意,必须定义Counted内的静态成员。对于numObjects来说,这很容易——我们只需要在Counted的实现文件里定义它即可:

template<class BeingCounted> // 定义numObjects

int Counted<BeingCounted>::numObjects; // 自动把它初始化为0

对于maxObjects来说,则有一些技巧。我们应该把它初始化为什么值呢?如果你想允许建立10个printer对象,我们应该初始化Counted<Printer>::maxObjects为10。另一方面如果我们向允许建立16个文件描述符对象,我们应该初始化Counted<Printer>::maxObjects为16。到底应该怎么做呢?

简单的方法就是什么也不做。我们不对maxObject进行初始化。而是让此类的客户端提供合适的初始化。Printer的作者必须把这条语句加入到一个实现文件里:

const size_t Counted<Printer>::maxObjects = 10;

同样FileDescriptor的作者也得加入这条语句:

const size_t Counted<FileDescriptor>::maxObjects = 16;

如果这些作者忘了对maxObjects进行初始化,会发生什么情况呢?很简单:连接时会发生错误,因为maxObjects没有被定义。如果我们提供了充分的文档对Counted客户端说明了需求,他们会回去加上这个必须的初始化。

译者注:

translation unit - a source file presented to a complier with an object file produced as a result.

linkage - refers to whether a name is visible only inside or also outside its translation unit.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有