Support

Symbian Developer Network C++ Code Clinic

July 2008: Can I Leave in a Destructor?

Code Clinic

Jo Stichbury re-opens the code clinic to dispense more advice about Symbian C++.

In this month's clinic she picks up from last month's discussion about leaving in constructors with some advice about leaving in the destructors of stack-based T class objects.

Prior to Symbian OS v9.1, a leave did not call the destructor of a stack-based object, but simply deallocated the stack space occupied by the object. Classes that were used on the stack could not be leave-safe if they relied on a destructor for cleanup. This lead to the rule that T and R classes must not have destructors.

In Symbian OS v9.1, the underlying implementation of Symbian OS leaves was changed to use C++ exceptions [R1]. Now, if a leave occurs, the destructors of stack-based objects are called. So if you work with only Symbian OS v9.1 or later, you can create a T class with a destructor that will be called as the stack unwinds when a leave occurs [N1]. However, if you do add a destructor to a T class, there is a limitation that you should be aware of:

You should not allow leaves or exceptions to occur in the destructor of any stack-based object on Symbian OS.

This code clinic examines the reasons for the rule. So, first of all, let's take a step back and look at how Symbian OS manages nested exceptions.

Symbian OS Says "No!" to Nested Exceptions

When a C++ exception is thrown, memory must be allocated for the exception object. A nested exception occurs when an exception is thrown while another is already being handled. For the second exception, a separate exception object is required, and if a further exception occurs while handling that one, another exception object is required, and so on. If the number of levels of nesting is unbounded, so is the number of allocations of exception objects. This is unacceptable for Symbian OS to support, particularly given that many exceptions occur primarily because code leaves as a result of an out of memory condition.

So, on Symbian OS, nested exceptions are not supported. When the code runs on hardware, only a single exception can be thrown and handled at a time. If another exception occurs while the first is being handled, abort() is called to terminate the thread. By restricting the memory requirement to a single exception object, memory can be pre-allocated to ensure that the exception object can always be created. In fact, when an exception occurs, if there is enough space on the heap the exception object is allocated there. But if not, the object is created using pre-allocated space on the stack.

Note that nested exceptions are supported on the Windows emulator, because exceptions are implemented using Win32 structured exception handling. Therefore, code using nested exceptions that executes correctly on the emulator will not function as intended when it is deployed to hardware.

A nested exception can easily occur in destructor code. This is because destructors are called as part of exception handling. If they throw an exception, it will be nested if it is thrown during cleanup initiated by exception handling. That's why you should never leave or throw and exception in a destructor.

The Anatomy of a Symbian OS Leave

Let's consider what happens when a leave occurs on Symbian OS. Firstly, User::Leave() is called.

  1. The cleanup stack is un-wound, and each item on it cleaned up (e.g. destructors are called for C classes at this point, Close() is called on any R class object made leave-safe by a call to CleanupClosePushL(), etc).
  2. An XLeaveException exception object is created and thrown, resulting in stack-unwinding as part of exception handling.
  3. The catch block (the TRAP) is executed.

During step 1, the cleanup stack takes responsibility for cleanup of, for example, heap-based C class objects, or it calls Close() on R class objects. At this point, no exception has been raised for the leave and, in the cleanup initiated by the cleanup stack, it is acceptable to leave or otherwise throw an exception, because it won't be nested.

During step 2, the normal stack unwinds and the destructors of stack-based objects are called. If a leave were to occur in one of those destructors, it would generate an exception that would be nested within the XLeaveException exception thrown for the original leave. On hardware, this would cause the thread to be terminated.

At step 3, exception handling is complete. Another leave or exception may occur without being nested in that exception thrown in step 2.

Hence if you write a destructor that leaves, it will not terminate the thread if the destructor is called by the cleanup stack (for example, for C class objects). However, if a destructor is called as the normal stack unwinds, you must not allow leaves or exceptions, because they have the potential to cause a nested exception that will terminate the thread when running on hardware.

The limitation also applies if you are using an approach like auto_ptr to ensure cleanup of heap based objects by using stack unwinding. If a stack-based auto_ptr destructor cleans up a heap-based object, the destructor code it calls must not leave or throw an exception either, since that too will be nested.

Conclusion

From Symbian OS v9.1, you can add a destructor to a T class, since it will be called to clean up a stack-based object in the event of a leave. However, you should not allow a leave or exception to occur in the destructor.

Although it is safe for heap-based C classes used on the cleanup stack to have destructors that leave, it is questionable whether it is ever sensible to leave in a destructor (since this suggests that the cleanup code may not completely execute, with the potential to cause memory or resource leaks). The standard advice for all C++ programmers, regardless of the platform they work upon, is never throw an exception in a destructor [R2].

Contributors

I'd like to thank David Caabeiro, Hamish Willee and Mark Shackman and for their contributions to this code clinic.

References and footnotes

[R1] Leaves and Exceptions
[R2] How can I handle a destructor that fails?

N1. From Symbian OS v9.1, it has been possible for T classes to own external data or handles since they can be cleaned up by a destructor call, even in the event of a leave. This introduced the option of using a T class as the equivalent of an auto_ptr, something that had previously been limited.

However, you should be aware of the implications of changing the behavior of a T class to the point that it is no longer a simple type class, analogous to the built-in types. If your class differs from the well-understood Symbian convention, there is a risk that it will be misused or used inappropriately.  For example, if an object of your T class is created on the heap and the cleanup stack is used to clean it up, the destructor will not be called, which risks a memory or resource leak.

If you make the class public and it is used by other developers who may make assumptions about its behavior based on their experience with legacy Symbian OS T classes, it is unwise to allow the behavior of the class to stray from the convention for T classes. But if you only ever intend to use the class internally in your code, and know that objects are only created on the stack, you can add a destructor if you need to.

The Code Clinic will feature regularly on Symbian Developer Network, on the first Friday of every month.  Got a Symbian C++ question? Send it to the Code Doctor at SDN@symbian.com. Questions used will receive a complementary copy of the Symbian Press book “Developing Software for Symbian OS, Second Edition”.

 

 
[standby-web] Terms of use | Privacy policy | Media Center | Contact us | © 2008 Symbian