要求在堆中产生对象
加入我们要求每个类必须在堆中建立对象。为了执行这个限制,你必须找到一种方法禁止调用new之外的其他手段建立对象。这很容易做到,non-heap-object在定义它的地方被自动构造,在生存期结束时被自动释放,所以只要禁止使用隐式构造函数和析构函数,就可以实现这种限制。
把这些调用变得不合法的一种最直接的方法是把构造函数和析构函数声明为private。这样做副作用太大,没有理由让这两个函数都是private。最好让析构函数private,构造函数public。你可以引进一个占用的伪析构函数,用来访问这真正的析构函数。客户端调用伪析构函数释放他们建立的对象。(WQ 加注:注意,异
常处理体系要求所有在栈中的对象的析构函数必须申明为公有!)
class UPNumber {
public:
UPNumber();
UPNumber(int initValue);
UPNumber(double initValue);
UPNumber(const UPNumber& rhs);
// 伪析构函数 (一个 const 成员函数, 因为
// 即使是 const 对象也能被释放。)
void destroy() const { delete this; }
...
private:
~UPNumber();
};
然后客户端这样进行程序设计:
UPNumber n; // 错误! (在这里合法, 但是 当它的析构函数被隐式地 调用时,就不合法了)
UPNumber *p = new UPNumber; //正确
...
delete p; // 错误! 试图调用 private 析构函数
p->destroy(); // 正确
另一种方法是把全部的构造函数声明为private。这种方法的缺点是一个类经常有很多构造函数,类的作者必须记住把它们都声明为private。否则如果这些函数就会由编译器生成,构造函数包括拷贝构造函数,也包括缺省构造函数;编译期生成的函数总是public的。因此仅仅声明析构函数为private是很简单的,因为每个类仅有一个析构函数。
通过限制访问一个类的析构函数或者它的构造函数来阻止建立non-heap object。这种方法也禁止了继承和包容:
class UPNumber { ... }; // 声明析构函数或构造函数 为 private
class NonNegativeUPNumber: public UPNumber { ... }; // 错误! 析构函数或 构造函数不能编译
class Asset {
private:
UPNumber value; // 错误! 析构函数或 构造函数不能编译
...
};
这些困难不是不能克服的。通过把 UPNumber 的析构函数声明为 protected(同时它的构造函数还保持 public)就可以解决继承的问题,需要包含 UPNumber 对象的类可以修改为包含指向 UPNumber 的指针:
class UPNumber { ... }; // 声明析构函数为 protected
class NonNegativeUPNumber: public UPNumber { ... }; // 现在正确了; 派生类 能够访问 protected 成员
class Asset {
public:
Asset(int initValue);
~Asset();
...
private:
UPNumber *value;
};
Asset::Asset(int initValue) : value(new UPNumber(initValue)) // 正确
{ ... }
Asset::~Asset() { value->destroy(); } // 也正确
禁止堆对象
通常对象的建立有三种情况:
- 对象被直接实例化
- 对象作为派生类的基类被实例化
- 对象被嵌入到其他对象内。
禁止用户直接实例化对象很简单,因为总是调用new来建立这种对象,你能够禁止用户调用new。你不能影响new操作符的可用性(这是内嵌于语言中),但是你能利用new操作符总是调用operator new函数这点来达到目的。你可以自己声明这个函数,而且你可以把它声明为 private。例如,如果你想不想让用户在堆中建立 UPNumber对象,你可以这样编写:
class UPNumber {
private:
static void *operator new(size_t size);
static void operator delete(void *ptr);
...
};
现在用户仅仅可以做允许它们做的事情:
UPNumber n1; // okay
static UPNumber n2; // also okay
UPNumber *p = new UPNumber; // error! attempt to call
// private operator new
如果你也想禁止 UPNumber 堆对象数组,可以把 operator new[]和 operator delete[](也声明为 private
有趣的是,把 operator new 声明为 private 经常会阻碍 UPNumber 对象做为一个位于堆
中的派生类对象的基类被实例化。因为 operator new 和 operator delete 是自动继承的,
如果 operator new 和 operator delete 没有在派生类中被声明为 public(进行改写,
overwrite),它们就会继承基类中 private 的版本,如下所示:
class UPNumber { ... }; // 同上
class NonNegativeUPNumber: //假设这个类
public UPNumber { //没有声明 operator new
...
};
NonNegativeUPNumber n1; // 正确
static NonNegativeUPNumber n2; // 也正确
NonNegativeUPNumber *p = new NonNegativeUPNumber; // 错误! 试图调用 private operator new
如果派生类声明它自己的operator new,当在堆中分配派生对象时,就会调用这个函数。于是需要另一种不同的方法来防止UPNumber成员的分配问题。UPNumber的private operator new,不会对对包含 UPNumber 成员对象的对象的分配产生任何影响:
class Asset {
public:
Asset(int initValue);
...
private:
UPNumber value;
};
Asset *pa = new Asset(100);