Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

2

Leaves: Symbian OS Exceptions

Go away. I’m all right

Said to be the last words of H. G. Wells

Symbian OS was first designed at a time when exceptions were not part of the C++ standard. Later, exception handling was introduced to the standard, but was found to add substantially to the size of compiled code and to run-time RAM overheads, regardless of whether or not exceptions were actually thrown. For these reasons, standard C++ exception handling was not considered suitable to add to Symbian OS, with its emphasis on a compact operating system and client code. When compiling Symbian OS code, the compilers are explicitly directed to disable C++ exception handling, and any use of the try, catch or throw keywords is flagged as an error.

An alternative to conventional, but rather awkward, error-checking around each function with a return value was needed. Thus ”leaves”1 were developed as a simple, effective and lightweight exception-handling mechanism which has become fundamental to Symbian OS. You’ll encounter lots of ”leaving code” when working on Symbian OS and you’ll probably write some too. You need to know how to recognize code that leaves and how to use it efficiently and safely, since it’s possible to leak memory inadvertently in the event of a leave. So when and how does a leave occur and why would you use it?

2.1 Leaving Functions

A leave may occur when you call a leaving function or if you explicitly call a system function to cause a leave. A leave is used to raise an

1 ”Leaves” is as in the verb ”to leave” rather than the noun usually found attached to plants. A function that contains code which may leave is called a ”leaving function” while code that has executed along the path of a leave (say, as the result of an exceptional condition) can be said to have ”left”.

14

LEAVES: SYMBIAN OS EXCEPTIONS

exception and propagate an error value back up the call stack to a point at which it can be ”caught” by a trap harness and handled appropriately. To all intents and purposes, code execution ends at the point of the leave and resumes where it is trapped. The leave sets the stack pointer to the context of a trap harness TRAP macro and jumps to the desired program location, restoring the register values. It does not terminate the flow of execution (unlike an assertion, which is used to detect programming errors and panic accordingly, as described in detail in Chapter 16).

TRAP and User::Leave() may be considered analogous to the standard library setjmp() and longjmp() methods respectively. A call to setjmp() stores information about the location to be ”jumped to” in a jump buffer, which is used by longjmp() to determine the location to which the point of execution ”jumps”. A leave should only be used to propagate an exception to a point in the code which can handle it gracefully, unwinding the call stack as it does so. It should not be used to direct the normal flow of program logic.

A typical leaving function is one that performs an operation that is not guaranteed to succeed, such as allocation of memory, which may fail under low memory conditions. Since leaving functions by definition leave with an error code (a ”leave code”), they do not also need to return error values. Indeed any error that occurs in a leaving function should be passed out as a leave; if the function does not leave it is deemed to have succeeded and will return normally. Generally, leaving functions should return void unless they use the return value for a pointer or reference to a resource allocated by the function. Later in this chapter, I’ll discuss the factors that may influence your decision as to whether to implement a function that leaves or one that returns an error value.

Some examples of leaving function declarations are as follows:

void InitializeL();

static CTestClass* NewL();

RClangerHandle& CloneHandleL();

If a function may leave, its name must be suffixed with ”L” to identify the fact. You must use this rule: of all Symbian OS naming conventions it is probably the most important. If you don’t name a leaving function accordingly, callers of your code may not defend themselves against a leave and may potentially leak memory.

Functions may leave if they:

• call code that may leave without surrounding that call with a trap harness

call one of the system functions that initiates a leave, such as

User::Leave() or User::LeaveIfError()

LEAVING FUNCTIONS

15

use the overloaded form of operator new which takes ELeave as a parameter (described in Section 2.2).

The suffix notation for names of functions which may leave is a simplification of the C++ exception specification which uses throw(. . .) or throw(type) by convention to indicate a function which may throw an exception. A call to User::Leave() or User::LeaveIfError() is similar to a C++ throw instruction (except for its destruction of stackbased variables, as I’ll discuss shortly) while the TRAP macros are, in effect, a combination of try and catch.

User::LeaveIfError() tests an integer parameter passed into it and causes a leave (using the integer value as a leave code) if the value is less than zero, for example, one of the KErrXXX error constants defined in e32std.h. User::LeaveIfError() is useful for turning a nonleaving function which returns a standard Symbian OS error into one that leaves with that value.

User::Leave() doesn’t carry out any value checking and simply leaves with the integer value passed into it as a leave code. User::LeaveNoMemory() also simply leaves but the leave code is hardcoded to be KErrNoMemory which makes it, in effect, the same as calling User::Leave(KErrNoMemory).

User::LeaveIfNull() takes a pointer value and leaves with KErrNoMemory if it is NULL. It can sometimes be useful, for example, to enclose a call to a non-leaving function which allocates memory and returns a pointer to that memory or NULL if it is unsuccessful.

The following example shows four possible leaves:

TInt UseClanger(CClanger* aClanger);

// Forward declaration

CClanger* InitializeClangerL()

 

 

{

 

 

CClanger* clanger = new (ELeave) CClanger(); // (1)

Leaves if OOM

CleanupStack::PushL(clanger);

// (2)

See Chapter 3

clanger->InitializeL();

// (3)

May leave

User::LeaveIfError(UseClanger(clanger));

// (4)

Leaves on error

CleanupStack::Pop(clanger);

 

 

return (clanger);

 

 

}

 

 

 

 

 

The L suffix is not checked during compilation so occasionally you may forget to append L to a function name, or may later add code to a previously non-leaving function which may then cause it to leave. Symbian OS provides a helpful tool, LeaveScan, that checks code for incorrectly-named leaving functions. It is described in more detail in Section 2.6.

If a function may leave, its name must be suffixed with ”L”.

16

LEAVES: SYMBIAN OS EXCEPTIONS

2.2 Heap Allocation Using new (ELeave)

Let’s take a closer look at the use of new (ELeave) to allocate an object on the heap. This overload leaves if the memory is unavailable, and thus allows the returned pointer to be used without a further test that the allocation was successful. We saw it used in the code fragment above:

CClanger* InitializeClangerL()

{

CClanger* clanger = new (ELeave) CClanger();

CleanupStack::PushL(clanger);

clanger->InitializeL();

CleanupStack::Pop(clanger);

return (clanger);

}

The code above is preferable to the following code, which requires an additional check to verify that the clanger pointer has been initialized:

CClanger* InitializeClangerL()

{

CClanger* clanger = new CClanger();

if (clanger)

{

CleanupStack::PushL(clanger);

clanger->InitializeL();

CleanupStack::Pop(clanger);

}

return (clanger);

}

What exactly does new (ELeave) do? Well, when you call new() to allocate an object on the heap you are invoking the new operator. This first allocates the memory required for the object by calling operator new (yes, the naming scheme is incredibly confusing), passing in the size of the memory required. It then calls the constructor to initialize an object in that memory. This code is generated by the compiler, because it’s not possible to call a constructor directly – which of course means that if you want an object constructed on the heap, you must allocate it, via the new operator, through a call to new().

Symbian OS has overloaded the global operator new to take a TLeave parameter in addition to the size parameter provided implicitly by new(). The TLeave parameter is ignored by operator new and is only used to differentiate this form of operator new from the nonleaving version. The Symbian OS overload calls a heap allocation function that leaves if there is insufficient heap memory available:

// From e32std.h

enum TLeave {ELeave};