Techniques for requiring or prohibiting heap-based objects
Requiring Heap-Based Objects
1. The straightforward way
The brutle way is to declare the constructors and the destructor private
, but this is overkill. Either one of them need to be private to ensure objects only be created on the heap. Since there are usually many constructors but only one destructor per class, a more elegant way is to make the destructor private and the constructors public, prividing privileged pseudo-destructor function (which has access to the real destructor) for clients to call, as suggested in MECpp item 26.
Example:
|
|
Clients would program like this:
|
|
2. Inheritance-and-containment friendly
Restricting access to a class’s destructor or constructors prevents the creation of not only non-heap objects, but also both inheritance and containment. To work this out:
- To be friendly for inheritance, we declare
protected
forUPNumber
’s destructor. - To be friendly for containment, classes that need objects of type
UPNumber
can be modified to contain pointers toUPNumber
object instead:
|
|
3. Determining Whether an Object is On The Heap
It’s hard to tell whether an object is on the heap. For example, given the class definition above, it’s ligal to define a non-heap NonNegativeUPNumber
object, which will not construct its base UPNumber
part on the heap. There is no way to detect whether a constructor is being invoked as the base class part of a heap-based object, which means for the following contexts, it is not possible for the UPNumber
constructor to detect the difference:
|
|
Sadly, there’s no portable way to determine whether an object is on the heap, and there isn’t even a semi-portable way that works most of the time. Detailed dicussion on this topic could be found at Comments on Item 27 of More Effective C++.
We’ll have to turn to unportable, implementation-dependent system calls if we absolutely have to tell whether an address is on the heap. That being the case, we’d better off trying to redesign the software so we don’t need to determine whether an object is on the heap in the first place.
4. Determine whether it’s safe to delete a pointer
To answer this easier question, all we need to do is to create a collection of addresses that have been returned by operator new
. One possible solution is to provide an abstract mixin base class that offers derived classes the ability to determine whether a pointer was allocated from operator new
:
|
|
|
|
Note that dynamic_cast
is applicable only to pointers to objects that have at least one virtual function, and dynamic_cast
ing a pointer to void*
(or const void*
or volatile void*
or const volatile void*
) yields a pointer to the beginning of the memory for the object pointed to by the pointer. Here dynamic_cast
ing this
to const void*
gives us a pointer to the beginning of the memory for the current object, which is the pointer previously returned by HeapTracked::operator new
as long as the memory for the current object was allocated by HeapTracked::operator new
in the first place.
To use this basic class, say we want to be able to determine whether a pointer to an Asset
object points to a heap-based object, simply modify Asset
’s class definition to specify HeapTracked
as a base class:
|
|
And we could query Asset*
pointers as follows:
|
|
Since built-in types such as int
and char
can’t inherit from anything, HeapTracked
can’t be used with these built-in types. Still, the most common reason for wanting to use a class like HeapTracked
is to determine whether it’s okay to delete this
, and we don’t want to do that with a built-in type because such types have no this
pointer.
Prohibiting Heap-Based Objects
To preventing objects from being allocated on the heap, there are three cases:
- objects that are directly instantiated
- objects instantiated as base class parts of derived class objects
- objects embedded inside other objects
1. Preventing directly instantiation on heap
To prevent clients from directly instantiating objects on the heap: we can declare operator new
(and possibly operator new[]
) as private
:
|
|
|
|
Declaring operator new
private often also prevents UPNumber
objects from being instantiated as base class parts of heap-based derived class objects, because operator new
and operator delete
are inherited, so if these functions aren’t declared public in a derived class, that class inherits the private versions declared in its base(s):
|
|
2. Preventing base class parts instantiated on heap
However, if the derived class declares an operator new
of its own, which will be called when allocating derived class objects on the heap, it is hard to prevent UPNumber
base class parts from winding up there.
3. Preventing base class parts instantiated on heap
Similarly, the fact that UPNumber
’s operator new
is private has no effect on attempts to allocate objects containing UPNumber
objects as members:
|
|
Just as there’s no portable way to determine if an address is on the heap, however, there is no portable way to determine that it is not on the heap, so we can’t throw an exception in the UPNumber
constructors if a to-be-tested UPNumber
object being constructed is on the heap. We’re out of luck.