更多 C++ 惯用法/要求或禁止基于堆的对象
- 要求或阻止在堆上创建对象
C++ 支持不同的创建对象的方式:基于堆栈的(包括临时对象)、基于堆的以及具有静态存储期的对象(例如,全局对象、命名空间范围内的对象)。有时限制类对象的创建方式会很有用。例如,框架可能要求用户仅创建基于堆的对象,以便它可以控制对象的生存期,而不管创建这些对象的函数是什么。同样,禁止在堆上创建对象也很有用。C++ 有惯用的方法来实现这一点。
要求基于堆的对象
在这种情况下,程序员必须使用 new
创建对象并禁止基于堆栈的对象和具有静态存储期的对象。其想法是阻止访问始终用于基于堆栈对象的函数之一:构造函数或析构函数。阻止访问构造函数,即受保护的/私有的构造函数,也会阻止基于堆的对象。因此,唯一可用的方法是创建一个受保护的析构函数,如下所示。请注意,受保护的析构函数也会阻止全局和命名空间范围内的对象,因为这些对象最终会被销毁,但析构函数是不可访问的。同样,这也适用于临时对象,因为销毁临时对象需要一个公共析构函数。
class HeapOnly {
public:
HeapOnly() {}
void destroy() const { delete this; }
protected:
~HeapOnly() {}
};
HeapOnly h1; // Destructor is protected so h1 can't be created globally
HeapOnly func() // Compiler error because destructor of temporary is protected
{
HeapOnly *hoptr = new HeapOnly; // This is ok. No destructor is invoked automatically for heap-based objects
return *hoptr;
}
int main(void) {
HeapOnly h2; // Destructor is protected so h2 can't be created on stack
}
受保护的析构函数还阻止访问 delete HeapOnly
,因为它内部调用了析构函数。为了防止内存泄漏,提供了 destroy
成员函数,它在自身上调用 delete
。派生类可以访问受保护的析构函数,因此 HeapOnly
类仍然可以作为基类使用。但是,派生类不再具有相同的限制。
禁止基于堆的对象
可以通过禁止访问所有形式的特定于类的 new
运算符来阻止对象的动态分配。标量对象的 new
运算符和对象数组的 new
运算符是两种可能的变体。两者都应声明为受保护的(或私有的),以防止基于堆的对象。
class NoHeap {
protected:
static void * operator new(std::size_t); // #1: To prevent allocation of scalar objects
static void * operator new [] (std::size_t); // #2: To prevent allocation of array of objects
};
class NoHeapTwo : public NoHeap {
};
int main(void) {
new NoHeap; // Not allowed because of #1
new NoHeap[1]; // Not allowed because of #2
new NoHeapTwo[10]; // Not allowed because of inherited protected new operator (#2).
}
上述受保护的 new
运算符声明阻止了剩余的编译器生成的版本,例如放置 new 和 nothrow new。仅将标量 new
声明为受保护的是不够的,因为它仍然可以执行 new NoHeap[1]
。受保护的 new []
运算符阻止了所有大小(包括大小为 1)的数组的动态分配。
限制对 new
运算符的访问还阻止派生类使用动态内存分配,因为 new 运算符和 delete 运算符是继承的。除非这些函数在派生类中被声明为 public,否则该类将继承其基类中声明的受保护的/私有的版本,从而阻止动态分配。