当前位置: 首页 > news >正文

More Effective C++之技术Techniques,Idioms,Patterns_条款26-27

More Effective C++之技术Techniques,Idioms,Patterns

  • 条款26:限制某个class所能产生的对象数量
  • 条款27:要求(或禁止)对象产生于heap之中
    • 要求对象产生于heap之中(译注:所谓Heap-Based Objects)
    • 判断某对象是否位于heap内
    • 禁止对象产生于heap中

条款26:限制某个class所能产生的对象数量

    对于限制class的数目,我们最常见的做法是使用单例模式,将构造函数定义为私有,然后定义一个static对象,可定义于class中,亦可定义在static 函数中,然后用户通过该static函数返回该对象,本条款建议定义在static函数中。主要是为了更确切地确定对象构造时间(第一次调用该static function);示例代码如下:

class Printer {
public:static Printer& thePrinter();...
private:Printer();Printer(const Printer& rhs);...
};
Printer & Printer::thePrinter() {static Printer p;return p;
}

    此外,为了避免名字冲突可以将类和函数定义到命名空间中;主要核心思想还是为了解决对象顶多只产生一个对象的问题。
    本条款给出了另一种情况,对象可能需要产生多个(大于1个)分别适用于不同场景。此时使用上述的的单例模式就显得有点力不从心了;所以本条款,采用了静态变量对对象个数使用引用计数的方式,进行处理,同时限制对象的最大数进行制约;从而达到限制class所能产生的对象数目的目的。本条款,一开始直接在Printer中增加了static numberObjects及maxObjects的方式,约束对象的数目;实现后,发现可以将该逻辑定义到一个基类中,对于有相同限制需要的类,直接继承该类即可达到限制目的。

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 int maxObjects;void init();
};
template <class BeingCounted>
Counted<BeingCounted>::Counted() 
{ init(); }
template<class BeingCounted>
int Counted<BeingCounted>::numObjects;
template <class BeingCounted>
Counted<BeingCounted>::Counted(const Counted<BeingCounted>&) 
{ init(); }
template <class BeingCounted>
void Counted<BeingCounted>::init() { if (numObjects >= maxObjects) throw TooManyObjects();++ numObjects;
}

    从类的定义来看,事实上就是通过numObjects >= maxObjects的条件约束,如果满足了该条件就会抛出异常,从而限制了对象的数量。针对开头定义的Printer需要约束对象的大小:只需要做如下改动:

class Printer : private Counted<Printer> {
public:static Printer * makePrinter() { return new Printer(); }static Printer * makePrinter(const Printer& job) { return new Printer(job); } void reset();...using Counted<Printer>::objectCount;using Counted<Printer>::TooManyObjects;
private:Printer();Printer(const Printer& rhs);
};

    此处Printer只需集成字Counted<Printer>即可完成对类对象的数量;实际数量可通过

template<>
const int Counted<Printer>::maxObjects = 1;

    注意该语句必须指定,否则会导致链接失败;比如实际需要限制的数量为10,则可修改为

template<>
const int Counted<Printer>::maxObjects = 10;

条款27:要求(或禁止)对象产生于heap之中

    本条款介绍两种场景,一种是对象只产生于heap中,另一种是禁止对象产生于heap中;从实现细节上来看,实现方法都会存在一定的瑕疵,权当做一种借鉴方法。

要求对象产生于heap之中(译注:所谓Heap-Based Objects)

    因为non-heap objects会在其定义点自动构造,并在跳出有效范围后自动析构,所以我们只要让那些被隐式调用的构造动作或析构动作不合法,就可以了。
    为了使这些动作不合法,最直截了当的方式是将constructor和destructor声明为private。事实上,没必要把它们俩都声明为private,比较好的办法是让destructor成为private,而constructor仍为public。如此一来,我们可以导入一个pseudo(伪的)destructor函数,用来调用真正的destructor。Clients则调用这个psedu-destructor以销毁它们所产生的对象。
    示例代码如下:

class Base {
public:Test();...// pseudo destructorvoid destroy() const { delete this; }
private:~Base();
};

    使用Base类,于是这么写:

Base test;												// 编译报错,因为析构函数为private
Base *pTest = new Base;
...
delete pTest;											// 编译错误,企图调用private destructor
p->destroy();											// 良好。

    另一个办法是将constructors都声明为private;但是由于编译器会默认产生copy constructor,default constructor且为public,如此代码需要把所以这些默认的构造函数都罗列在private下,所以没有把destructor声明为private来得简洁。因为一个class只能有一个destructor。
    只要限制了destructor或constructor的运用,便可阻止non-heap objects的诞生。但是,它也妨碍而来继承(inheritance)和内含(containment):

class Inhr : public Base {
public:Inhr() : Base(){}
};
class Comp {
private:Base base;
};

    以上代码会出现编译错误,在class Inhr因为Base的构造函数为private,导致函数调用destroy内部调用~Base会失败;将destructor修改为protected即可:

class Base {
public:Base() {}// pseudo destructorvoid destroy() const { delete this; }
protected:~Base() {}
};class Inhr : public Base {
public:Inhr() : Base(){}
};

    对于包含元素则使用包含指针的方式进行处理:

class Comp {
public:Comp() {test = new Base;}~Comp() {test->destroy();}
private:Base *test;
};

判断某对象是否位于heap内

    该小节主要是利用了内存分配机制,栈空间地址通常位于高位地址,堆空间通常位于低位地址空间,但是该判断对于可移植性不是很强,以此为依据写出来的代码通用性不是很强,此处就不做过多的介绍,

禁止对象产生于heap中

    “检验对象是否位于heap中”的判断不是很清晰。“阻止对象分配于heap中”则是另一种手段。为了实现该目的,方案相对清晰。一般而言有3种可能:(1)对象被直接实例化;(2)对象被实例化为derived class objects内的“base class成分”;(3)对象内嵌于其他对象之中。让我们一一讨论。
    欲阻止clients直接将对象实例化于heap之中,很容易,因为此等对象总是以new产生出来,我们可以让clients无法调用new。虽然不能影响new operator的能力,但我们可以利用一个事实:new operator总是调用 operator new,而后者是我们可以自行声明的。更明确地说,我们可以将它声明为private或protected。示例代码如下:

class Base {
public:Base() {}
private:static void* operator new (size_t size) ;static void operator delete (void* ptr);
};

    现在clients只能够做某些被允许的事情

Base base;												// 可以
static Base sBase;										// 也可以
Base *pBase = new Base;						   			// 编译错误!企图调用private operator new

    将operator new声明为private应该足够了,但如果operator new属性为private 而operator delete却为public,看上去略奇怪,所以把它们俩都声明为了private。此外,为了禁止new Base[]的调用,我们同样需要把operator new[]及operator delete[]声明为private。
    有趣的是,将operator new声明为private,往往也会妨碍Base被实例化为heap-base derived class objects的“base class 成分”。那是因为operator new和operator delete都会被继承,所以如果这些函数不在derived class内声明为public,derived class继承了base(s)所声明的private版本:

class D : public Base {
};
D d;													// 没问题
static D sd;											// 没问题
D *p = new D;											// 错误!企图调用private operator new

    如果derived class声明有一个属于自己的operator new且为public,当clients将derived class objects声明于heap内时,该operator new函数会被调用,因此我们必须另觅良方以求阻止“D的Base class成分”的诞生。类似情况,当我们企图分配一个“内含Base对象”的对象,“Base的operator new乃为private”这一事实并不会带来什么影响:

class X {
Base base;
};
X *pX = new X;											// 没问题,调用的是X::operator new而非Base::operator new

    对使用目的而言,这把我们带回熟悉的情境。我们曾经希望“如果一个Base对象构造于heap以外,那么Base constructor就抛出exception”,这次我们的希望是“如果对象被产生于heap内的话,就抛出一个exception”。然而,就像没有任何据可移植性的做法可以判断某地址是否位于heap内一样,我们也没有据移植性的做法可以判定它是否不在heap内。这应该不令人惊讶;
    由此,本条款,给了我们两个一定程度上解决要求(或禁止)对象产生于heap之中。因为,clients可以通过友元、函数重载或者成员变量的方式绕过以上定义的private/protected定义,从而使通过以上方法的限制失效。


http://www.mrgr.cn/news/81477.html

相关文章:

  • Excel生成DBC脚本源文件
  • 【Rust自学】6.3. 控制流运算符-match
  • torch.nn.init 模块介绍
  • Flink中并行度和slot的关系——任务和任务槽
  • Go语言zero项目服务恢复与迁移文档
  • C++软件设计模式之享元模式(FlyWeight)
  • 【Hot100刷题计划】Day04 栈专题 1~3天回顾(持续更新)
  • 细说STM32F407单片机通过IIC读写EEPROM 24C02
  • 【ES6复习笔记】Spread 扩展运算符(8)
  • 基础运维学习计划-base版
  • 【golang】map遍历注意事项
  • 【ES6复习笔记】解构赋值(2)
  • 知识碎片-环境配置
  • Es搭建——单节点——Linux
  • 【ES6复习笔记】Map(14)
  • 常规配置、整合IDEA
  • Android 常用三方库
  • 硬件模块常使用的外部中断及中断优先级
  • ESP32_H2(IDF)学习系列-ADC模数转换(连续转换)
  • Python:模拟(包含例题:饮料换购 图像模糊 螺旋矩阵)
  • (长期更新)《零基础入门 ArcGIS(ArcMap) 》实验五----土地整治(超超超详细!!!)
  • YOLOv10目标检测-训练自己的数据
  • JS进阶-手写Promise
  • DP83848以太网移植流程,可以TCP通信
  • 基于Jenkins+Docker的自动化部署实践——整合Git与Python脚本实现远程部署
  • 大模型+安全实践之春天何时到来?