堆中建立对象
为了执行这种限制,你必须找到一种方法禁止以调用new
以外的其它手段建立对象。这很容易做到。非堆对象在定义它的地方被自动构造,在生存时间结束时自动被释放,所以只要禁止使用隐式的构造函数和析构函数,就可以实现这种限制。
把这些调用变得不合法的一种最直接的方法是把构造函数和析构函数声明为 private
这样做副作用太大。没有理由让这两个函数都是 private
。最好让析构函数成为 private
,让构造函数成为 public
。
例如,如果我们想仅仅在堆中建立代表无限精确度数字的对象,可以这样做:
class UPNumber
{
public:
UPNumber();
UPNumber(int initValue);
UPNumber(double initValue);
UPNumber(const UPNumber& rhs);
void destroy() const {
delete this; } // 伪析构函数 (一个 const 成员函数, 因为即使是 const 对象也能被释放。)
...
private:
~UPNumber();
};
然后客户端这样进行程序设计:
UPNumber n; // 错误! (在这里合法, 但是当它的析构函数被隐式地调用时,就不合法了)
UPNumber *p = new UPNumber; //正确
...
delete p; // 错误! 试图调用private 析构函数
p->destroy(); // 正确
另一种方法是把全部的构造函数都声明为 private
。
这种方法的缺点是:一个类经常有许多构造函数,类的作者必须记住把它们都声明为 private
。否则如果这些函数就会由编译器生成,构造函数包括拷贝构造函数,也包括默认构造函数;编译器生成的函数总是 public
。因此仅仅声明析构函数为 private
是很简单的,因为每个类只有一个析构函数。
上面这种方法虽然可行,但是这种方法也禁止了继承和包含。
class UPNumber {
... }; // 声明析构函数或构造函数为 private
class NonNegativeUPNumber: public UPNumber {
... }; // 错误! 析构函数或构造函数不能编译
class Asset
{
private:
UPNumber value;
... // 错误! 析构函数或 构造函数不能编译
};
这些困难也不是不能克服,通过把 UPNumber
的析构函数声明为 protected
就可以解决继承的问题,需要包含 UPNumber
对象的类可以修改为包含指向 UPNumber
的指针:
判断一个对象是否在堆中
上述粗略的类定义表明一个非堆的 NonNegativeUPNumber
对象是合法的:
NonNegativeUPNumber n; // 正确
那么现在 NonNegativeUPNumber
对象 n
中的 UPNumber
部分也不在堆中,这样说对么?
所有 UPNumber
对象 —即使是做为其它派生类的基类—也必须在堆中。我们如何能强制执行这种约束呢?
没有简单的办法。UPNumber
的构造函数不可能判断出它是否做为堆对象的基类而被调用。也就是说对于 UPNumber
的构造函数来说没有办法侦测到下面两种环境的区别:
NonNegativeUPNumber *n1 = new NonNegativeUPNumber; // 在堆中
NonNegativeUPNumber n2; //不再堆中
或许你认为你能够在 new
操作符、operator new
和 new
操作符调
用的构造函数的相互作用中操作一波。你可能会这样修改:
class UPNumber
{
public:
// 如果建立一个非堆对象,抛出一个异