Why do I have to allocate C++ Slice classes and servant classes on the heap?

The Ice C++98 mapping uses reference counting to free you from the burden of managing memory: Once you have assigned a newly-allocated class instance to a smart pointer (Ptr) variable, you no longer have to worry about memory leaks. Even in the presence of exceptions, it is guaranteed that the memory for the instance will be deallocated correctly. (Thanks to the Ice garbage collector, this is true even in the presence of cyclic references among instances.)

Internally, Ice uses the same Ptr reference counting mechanism that it provides to developers, and for the same reasons. (Despite the legendary programming skills of ZeroC's staff, in truth, we are no better at manually managing memory than anyone else...) The reference counting is also visible in the Ice APIs. For example, the object adapter operation addWithUUID has the following signature:

C++
Ice::ObjectPrx addWithUUID(const Ice::ObjectPtr&); 

Note that the parameter type is const ObjectPtr&, that is, the operation expects a smart pointer to a class instance that is reference counted. This is a good thing because it allows you to write the following:

C++
adapter->addWithUUID(new MyClassI); 

This works because ObjectPtr has a constructor that accepts an Object* so, at the point of call, the compiler constructs a temporary ObjectPtr that it passes to addWithUUID. The creation of the temporary raises the reference count of the instance to 1. Internally, the object adapter assigns the passed ObjectPtr to another ObjectPtr in its Active Servant Map (ASM), which raises the reference count to 2; once addWithUUID completes, the compiler destroys the temporary ObjectPtr it created earlier, which lowers the reference count of the instance down to 1. This turns out to be very useful because the reference counting guarantees that the instance will not be destroyed until precisely the right time, namely, only after its ASM entry has been removed, and only after all operations that are executing inside the instance have completed. (See Issue 14 of Connections for an article describing how to use this feature to implement thread-safe life cycle operations.)

However, this same mechanism can bite you if you allocate class instances on the stack or in static variables. For example:

C++
MyClassI mc;
adapter->addWithUUID(&mc); // Looming disaster! 

The problem here is that, eventually, once the reference count of the instance drops to zero, the instance will call delete this. However, that is most likely going to be the last you see of your process: Calling delete on a stack-allocated instance is likely to incur the wrath of the operating system, which, if you are lucky, will unceremoniously kill the process and nicely commemorate the event with a tomb stone in the form of a core dump. (A stack trace from the core dump will show some error in the internals of the heap.) If you are unlucky, your program may go off and do completely unexpected and strange things because passing a pointer to delete that did not come from new causes "undefined behavior" (which is C++ standard legalese for "absolutely anything might happen").

The moral of this story is pure and simple: do not allocate Slice class instances or servant class instances on the stack or in static variables, not ever. It simply does not work, period.

So, can you protect yourself from this mistake? The answer is yes, you can, by using a little-known feature of C++: If a class has a protected destructor, instances of the class must be allocated on the heap; attempts to allocate an instance on the stack or in a static variable cause a compile-time error. So, if you have a destructor in your class, simply make it protected. If you don’t have a destructor already, simply add an empty protected destructor:

C++
class MyClassI : public MyClass
{
public:
  // . . .
protected:
  virtual ~MyClass() {}
}; 

That simple change now makes it impossible to incorrectly allocate a class instance:

C++
MyClassI mc;               // Compile-time error
adapter->addWithUUID(&mc); // We don't get this far. 

As an added benefit, making the destructor protected also prevents incorrect deletion of a class instance:

C++
MyClassI* p = new MyClassI;
// ...
delete p; // Compile-time error 

Again, this is a good thing: With the Ice C++ mapping, you are not meant to delete things by hand — deleting things is the job of Ptr variables, which are much better at it than programmers.

Ideally, the Slice compiler would protect you from incorrect allocation but, unfortunately, it cannot do that: The rules of C++ require the protected destructor to be present in the most-derived instance. (Adding a protected destructor to the MyClass base does not help because what is instantiated is the derived class MyClassI.) However, MyClassI is written by you, not by the compiler, so you have to add the protected destructor yourself. (The Slice compiler generates a protected destructor for non-abstract Slice classes. However, for abstract Slice classes (that is, Slice classes with operations) and for servant classes, you still have to do this yourself.)

So, you can either adhere to the firm rule of "only allocate instances on the heap" or, if you believe that an ounce of prevention is better than a pound of cure, you can habitually add a protected destructor to all your Slice classes and servant classes. It only takes a few seconds, and the cost in terms of memory footprint and performance is zero.

See Also