More C++ Idioms/Requiring or Prohibiting Heap-based Objects

Requiring or Prohibiting Heap-based Objects

edit

Intent

edit
  • Require or prevent creation of objects on heap

Also Known As

edit

Motivation

edit

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

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.

Known Uses

edit
edit

References

edit