Restrictions on the use of leaves and TRAPs in destructors

Outlines the restrictions on the use of leaves and TRAPs in destructors.

You cannot use TRAP (and TRAPD) and User::Leave() in a destructor, nor in any function that a destructor might call, for objects that have been placed onto the call stack. TRAP and User::Leave() are implemented in terms of C++ Try, Catch and Throw. This means that if an exception occurs, the call stack is "unwound". In doing this, the destructors of objects on the call stack are called. If any of these destructors subsequently throw an exception, this is termed a nested exception. While a nested exception is supported on the emulator (WINS), it is not supported on other hardware, and there is no guarantee that the thread will terminate cleanly.

Symbian coding standards state that objects placed onto the call stack should not have non-trivial destructors. By convention, such classes are identified by names having an initial T or R. This means that such destructors should not leave.

The following code fragment shows the kind of code that is not safe to use:

void f()  
    {
    TBar bar;
    // Function processing.
    ...
    // The bar object on the call stack is automatically destroyed
    // at function termination.
    }

TBar::~TBar()
    {
    // The destructor calls a function that can leave.
    // This is not permitted here.
    doCleanUpL();
    }

You can use TRAP (and TRAPD) and User::Leave() in a destructor and in any function that a destructor might call, for heap-based objects, i.e. objects that are destroyed by the cleanup stack. However, while this is permissible, it is not recommended practice.

Such objects are instances of classes derived from CBase, and by convention such classes are identified by names having have an initial C.

In principle, a destructor should never fail. If a destructor can leave, or a function called by the destructor can leave, it suggests that the code has been poorly architected. It also implies that part of the destruction process might fail, potentially leading to memory or handle leaks.

One possible approach to avoid using functions that can leave within a destructor is to have what might be referred to as ‘two-phase destruction’ where some form of ShutdownL function is called prior to deleting the object. If there are concerns about introducing additional complexities in API-usage (and greater risk), suitable guards can be introduced. One method might be to store the current state of the object internally and then use an ASSERT to check this in the destructor. This would ensure that any usage errors are discovered by very simple run time testing.

The following code fragment shows an example of two-phase destruction.

// Here, the shutdown function is a member of the class CMyClass,
// and performs destruction activity that can leave.

class CMyClass : public CBase
    {
public :
    IMPORT_C ~CMyClass();
    IMPORT_C void ShutdownL();
    ...
private :
    TBool iStateActive
    ...
    }

EXPORT_C void CMyClass::ShutdownL()
    {
       if (iStateActive == ETrue)
              {
              // Some destruction activity that can leave.
              iStateActive = EFalse;
              }
    }

EXPORT_C CMyClass::~CMyClass()
    {
       // Assert to ensure that ShutdownL() has already been called.
       ASSERT(iStateActive == EFalse);
    }