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定义,从而使通过以上方法的限制失效。