When should I use __setNoDelete with a smart pointer?

The Ice C++98 mapping provides reference-counting smart pointers that help to ensure your code does not leak memory. Once you assign the return value from operator new to a smart pointer, you no longer have to worry about memory leaks. For example:

Slice
class Node
{
    Node next;
}

This defines a simple self-referential class that can be used to create a linked list:

C++
{ // Open scope
    NodePtr p = new Node;
    p->next = new Node;
    // ...
} // Close scope, no leak here.

The reference counting that is performed by smart pointers takes care of deleting both Node instances once the variable p goes out of scope, even if the code leaves the scope in which p is defined due to an exception.

Smart pointers are instances of the IceUtil::Handle  template. For example, the Slice compiler generates the following type definition for NodePtr:

C++
typedef IceUtil::Handle<Node> NodePtr;

The Handle template contains member functions that take care of maintaining the reference count. The template provides overloaded constructors, a copy constructor, a destructor, and overloaded assignment operators to ensure that the pointed-at object's reference count is maintained appropriately. To maintain the reference count, these member functions of the Handle template expect the pointed-at object to provide two member functions, __incRef and __decRef, that increment and decrement the reference count, respectively. The pointed-at class provides these member functions by deriving from the IceUtil::Shared  base class, which also contains __getRef and __setNoDelete member functions:

C++
class Shared
{
public:
    Shared() : _ref(0), _noDelete(false) {}
    virtual ~Shared() {}
    void __incRef();
    void __decRef();
    void __getRef();
    void __setNoDelete(bool b)
    {
        _noDelete = b;
    }

private:
    int _ref; // Exact type varies with platform
    bool _noDelete;
};

The __getRef member function returns the value of the _ref member and is useful mainly for debugging. The implementation of __incRef increments the value of the _ref member, and __decRef decrements the value. Ignoring synchronization issues, __decRef is implemented something like this:

C++
void
Shared::__decRef()
{
    if(--_ref == 0 && !_noDelete)
    {
        delete this;
    }
}

In other words, when the smart pointer calls __decRef on the pointed-at instance and the reference count reaches zero (which happens when the last smart pointer for a class instance goes out of scope), the instance self-destructs by calling delete this.

However, as you can see, the instance self-destructs only if _noDelete is false (which it is by default, because the constructor initializes it to false). You can call __setNoDelete(true) to prevent this self-destruction and, later, call __setNoDelete(false) to enable it again. Why would you want to do this? As it turns out, doing this is necessary if, for example, a class in its constructor needs to pass this to another function:

C++
void
someFunction(const Handle<SomeClass>& h)
{
    // ...
}

// SomeClass derives from IceUtil::Shared
SomeClass::SomeClass()
{
    someFunction(this); // Trouble looming here!
} 

At first glance, this code looks innocuous enough. While SomeClass is being constructed, it passes this to someFunction, which expects a smart pointer. No problem with that: the compiler constructs a temporary smart pointer at the point of call (because Handle has a single-argument constructor that accepts a pointer to a heap-allocated instance, so the constructor acts a conversion function). However, this code fails badly. The SomeClass instance is constructed with a statement such as:

C++
SomeClassPtr p = new SomeClass;

The constructor of SomeClass is called by operator new and, when the constructor starts executing, the reference count of the instance is zero (because that is what the reference count is initialized to by the Shared base class of SomeClass). When the constructor calls someFunction, the compiler creates a temporary smart pointer, which increments the reference count of the instance and, once someFunction completes, the compiler dutifully destroys that temporary smart pointer again. But, of course, that drops the reference count back to zero and causes the SomeClass instance to self-destruct by calling delete this. The net effect is that the call to new SomeClass returns a pointer to an already deleted object! Of course, this is likely to cause havoc — a core dump is the most likely outcome, but the program may exhibit various difficult-to-diagnose forms of insanity before that happens.

To get around the problem, you can call __setNoDelete:

C++
SomeClass::SomeClass()
{
    __setNoDelete(true);
    someFunction(this);    // OK
    __setNoDelete(false);
}

The code disables self-destruction while someFunction uses its temporary smart pointer by calling __setNoDelete(true). By doing this, the reference count of the instance is incremented before someFunction is called and decremented back to zero when someFunction completes without causing the object to self-destruct. The constructor then re-enables self-destruction by calling __setNoDelete(false) before returning, so the statement:

C++
SomeClassPtr p = new SomeClass;

does the usual thing, namely to increment the reference count of the object to 1, despite the fact that a temporary smart pointer existed while the constructor ran.

Note that you may also come across a similar scenario if a derived class constructor throws an exception. This can happen, for example, if you use the listener pattern, for which a base class constructor passes this to the listener class, and an exception in the derived constructor can lead to double deallocation. You can use the same __setNoDelete mechanism to temporarily disable self-destruction in this case.

In general, whenever a class constructor passes this to a function or another class that accepts a smart pointer, you must temporarily disable self-destruction.

See Also