Exceptions and error handling

Symbian C++ uses its own exception mechanism consisting of TRAPs, Leaves, and the Cleanup Stack, which must be handled correctly for use with the standard C++ mechanism of try, catch and exceptions used by Qt. Symbian and Qt also make extensive use of error codes which require handling or translation when code from both APIs is used.

Symbian exceptions and errors

Symbian C++ uses its own exception mechanism consisting of TRAPs (somewhat equivalent to try), Leaves (a bit like throw), and the Cleanup Stack (which allows locally scoped heap-allocated objects to be safely deleted in the event of an exception).

Potentially leaving code is run inside a TRAP macro. In the event of a Leave, the call stack is unwound up to the TRAP, automatic variables are deallocated and the Cleanup Stack deletes or otherwise cleans up any objects on the current TRAP level. The TRAP macro returns the leave error code (a single integer) and execution continues immediately afterwards (there is no separate "catch"). Much like catch blocks, the code following a TRAP is expected to handle leave codes that it understands, and Leave again with any that it does not understand.

TInt result;
TRAP(result, MayLeaveL());
 
if (KErrNone!=result) {
// Deal with errors that can be handled at this level
// Leave again with any errors you choose not to handle
}

Note: TRAPs are relatively heavy weight in terms of executable size and RAM consumption. While these can be nested and used at any level, it is usually better to trap at a high level, grouping a number of Leaving functions.

Symbian uses a naming convention for functions and class types to help developers understand how to safely allocate objects to prevent memory leaks:
  • Functions that might Leave are by convention named with a trailing L (or LC to indicate that the object remains on the Cleanup Stack when the method returns).

  • Classes that should be allocated on the heap usually first derive from CBase and are named with the C prefix.

    • CBase provides zero member initialization on construction, an overload of new that Leaves (new (ELeave)) in the event of allocation failure, and a virtual destructor that the Cleanup Stack can use to delete the object in the event of a Leave. (The cleanup stack also provides methods to delete other heap-based objects that are not derived from CBase, such as arrays and interface classes.)

    • C classes are constructed using the leave-safe two-phase construction idiom.

  • The T prefix is used for stack-allocated classes that do not own pointers to heap resources and do not have a destructor.

  • The R prefix is used for stack classes that own resources elsewhere – and therefore require explicit support for cleanup.

In Symbian's original implementation, the destructors of automatic variables were not called in the event of a Leave. As a result smart pointers could not be used for local object cleanup. The naming conventions allow you to work out whether objects require explicit cleanup support via the cleanup stack (R, C classes) or not (T classes).

Exception Handling in the Symbian guide provides a more detailed discussion of how the exception mechanism works and how to write leave-safe code. Leave-safe object construction is discussed in the introduction to Two-phase construction and in more detail in Two Phase Construction in the Symbian Guide.

Note: At the time Symbian C++ was created, standard C++ try-catch-throw exception handling was considered too memory-expensive for an embedded operating system. In addition, there was limited support in the compilers in use at that time. Standard C++ exception support was added in Symbian OS v9.

Symbian's exception handling mechanism is now implemented in terms of try, catch and throw. You can use try, catch and throw in standard C++ code, and even mix them with Symbian C++ (with care).

Symbian functions use either Leaves or error codes to signal an exception condition. Returning an error is usually preferred to Leaving when the error condition is "expected" (for example, attempting to read past the end of a file). However, there are no firm rules (other than class construction always leaves with KErrNoMemory if there is no memory to allocate the object), and you will see errors and Leaves used in an ad-hoc manner within the Symbian C++ APIs. It is considered bad programming practice to have a function that leaves and returns an error, or to convert a leave to an error (using a TRAP without good reason).

There is a set of global error codes (negative integer values), defined in e32err.h. Other error codes are defined within individual components – there are a number of lists, including the one on www.newlc.com.

Symbian defines a second class of exception called a panic. A Panic is used to signal programmatic errors and results in immediate termination of the application – this is an appropriate response to misuse of an API.

Qt exceptions and error codes

Qt supports the standard C++ exception handling mechanism where it is available on the underlying platform and compiler.

Although Qt has supported the mechanism for third-party code since the beginning, historically Qt framework code has not used the mechanism (for reasons of cross-platform compatibility). Instead Qt code uses locally defined error codes to notify client code of error conditions. When there is a failure to allocate memory, framework code simply returns a null pointer and fails on first pointer dereference (except for some larger objects, where special measures are taken).

Qt 4.6 supports basic exception safety (component invariants are preserved and no resources are leaked) for Qt's container classes as well as for the QFile class:

  • The QScopedPointer smart-pointer has been added to allow locally scoped objects to be cleaned up in the event of an exception (during use or on construction).

  • Objects now throw std::bad_alloc if memory cannot be allocated (using q_check_ptr() to check the returned pointer).

  • try/catch blocks have been added where appropriate.

Qt does not (at the time of writing) define its own exception class hierarchy, and the framework code only throws std::bad_alloc. Qt itself still uses error codes rather than exceptions to propagate errors other than allocation failure. Developers should assume that almost any Qt class can throw, and that where it interacts with third-party code, it can throw virtually anything. (An exception is QScopedPointer's constructor, which means that it can safely be used for cleaning up locally scoped objects.) When Qt catches exceptions, it always rethrows them if they are not handled.

You should use the macros QT_TRY, QT_CATCH, QT_THROW, and QT_RETHROW (defined in qglobal.h) in preference to calling try, catch and throw directly. This ensures that exception handling code is only compiled on platforms that support exceptions. Note that if you write your application to be exception safe, it will work whether exceptions are enabled or not.

Mixing Symbian C++ and Qt exception handling

Qt uses standard C++ exceptions while Symbian C++ uses Leaves. Although Leaves are implemented in terms of exceptions, care needs to be taken to interleave the two idioms.

Symbian exception safety succinctly explains the issues, and describes the barrier functions that you can use to convert between the idioms.

The barrier functions allow you to catch standard exceptions and convert them to Symbian errors or leaves for propagation into Symbian code, and similarly to convert Symbian C++ leaves or errors into standard exceptions. The methods are listed here for convenience: qt_symbian_throwIfError(), q_check_ptr(), QT_TRAP_THROWING(), qt_symbian_exception2Error(), qt_symbian_exception2LeaveL(), QT_TRYCATCH_ERROR(), QT_TRYCATCH_LEAVING().

The barrier methods can only catch and convert standard exceptions – application-specific exceptions propagate through, and cause the application to terminate if they reach a TRAP. This is expected behavior – by convention code should catch and handle only those exceptions that it understands – all other exceptions must be re-thrown.

Note: It is tempting to catch all exceptions with catch (...) or QT_CATCH (...) to prevent them propagating to a TRAP and terminating the application. However, if you do this the error condition still exists – all you have done is remove any opportunity for the application to handle the error appropriately – the code may still leak memory or fail in an unpredictable and hard-to-debug manner.

There are a few cases that are worth more detailed consideration: where a function must not fail, and where it can fail but must not throw or leave. In both cases "must not fail" should be treated as "must not fail with a leave or standard exception" – other exceptions must be allowed to propagate.

Destructors are a case where a function must not fail (throw exceptions or Leave). When implementing a destructor:
  • Try to only use functions that will not fail

  • If you must call a function that can Leave, stop it from propagating using a TRAP macro.

  • If you must call a function that can throw, stop standard exceptions with a QT_CATCH (const std::exception&). Other exceptions must be allowed to propagate – do not QT_CATCH (...) without rethrowing the exception.

  • A logical consequence of this is that a Qt slot connected to the QObject::destroyed() signal must be implemented as if it were destructor code, following all the rules above (it is emitted in the QObject destructor).

Some functions can fail, but must not throw or leave. For example Symbian C++ callback methods that do not have the L suffix cannot leave because the calling code may not have been written to be leave-safe. Nor can it throw, because there may be a TRAP at a higher level:
  • If the function can return an error code, you can use the Qt barrier functions to convert leaves or standard exceptions to errors.

  • If the function prototype does not allow errors to be returned, you can absorb errors you understand but must throw others.

  • In both cases, non-standard exceptions must be allowed to propagate.

Copyright note

Most of the material in this topic is based with permission on a Symbian Foundation wiki article Apps:Using Qt and Symbian C++ Together . The version used was that available at Symbian Foundation on 3 November 2010. The content in this page is licensed under the Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License (http://creativecommons.org/licenses/by-sa/2.0/uk).