More C++ Idioms/Requiring or Prohibiting Heap-based Objects
Requiring or Prohibiting Heap-based Objects
[edit | edit source]Intent
[edit | edit source]- Require or prevent creation of objects on heap
Also Known As
[edit | edit source]Motivation
[edit | edit source]C++ supports different ways to create objects: stack-based (including temporary objects), heap-based, and objects of static storage duration (e.g., global objects, namespace-scope objects). Sometimes it is useful to limit the ways in which objects of a class can be created. For instance, a framework may require users to create only heap-based objects so that it can take control of the lifetime of objects regardless of the function that created them. Similarly, it may be useful to prohibit creation of objects on the heap. C++ has idiomatic ways to achieve this.
Solution and Sample Code
[edit | edit source]Requiring heap-based objects
In this case, programmers must use new
to create the objects and prohibit stack-based and objects of static duration. The idea is to prevent access to one of the functions that are always needed for stack-based objects: constructors or destructors. Preventing access to constructors, i.e., protected/private constructors will prevent heap-based objects too. Therefore, the only available way is to create a protected destructor as below. Note that a protected destructor will also prevent global and namespace-scope objects because eventually the objects are destroyed but the destructor is inaccessible. The same is applicable to temporary objects because destroying temporary objects need a public destructor.
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
}
Protected destructor also prevents access to delete HeapOnly
because it internally invokes the destructor. To prevent memory leak, destroy
member function is provided, which calls delete
on itself. Derived classes have access to the protected destructor so HeapOnly
class can still be used as a base class. However, the derived class no longer has the same restrictions.
Prohibiting heap-based objects
Dynamic allocation of objects can be prevented by disallowing access to all forms of class-specific new
operators. The new
operator for scalar objects and for an array of objects are two possible variations. Both should be declared protected (or private) to prevent heap-based objects.
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).
}
The above declaration of protected new
operator prevents remaining compiler-generated versions, such as placement new and nothrow new. Declaring just the scalar new
as protected is not sufficient because it still leaves the possibility of new NoHeap[1]
open. Protected new []
operator prevents dynamic allocation of arrays of all sizes including size one.
Restricting access to the new
operator also prevents derived classes from using dynamic memory allocation because the new operator and delete operator are inherited. Unless these functions are declared public in a derived class, that class inherits the protected/private versions declared in its base preventing dynamic allocation.