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 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.
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 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.
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.
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).
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.
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).