Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 7
Текст из файла (страница 7)
Indeed any error that occurs in a leaving function should bepassed out as a leave; if the function does not leave it is deemed to havesucceeded and will return normally. Generally, leaving functions shouldreturn void unless they use the return value for a pointer or referenceto a resource allocated by the function. Later in this chapter, I’ll discussthe factors that may influence your decision as to whether to implementa 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 identifythe fact. You must use this rule: of all Symbian OS naming conventionsit is probably the most important. If you don’t name a leaving functionaccordingly, callers of your code may not defend themselves against aleave and may potentially leak memory.Functions may leave if they:• call code that may leave without surrounding that call with atrap harness• call one of the system functions that initiates a leave, such asUser::Leave() or User::LeaveIfError()LEAVING FUNCTIONS15• use the overloaded form of operator new which takes ELeave as aparameter (described in Section 2.2).The suffix notation for names of functions which may leave is asimplification of the C++ exception specification which uses throw(.
. .)or throw(type) by convention to indicate a function which may throwan 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, ineffect, a combination of try and catch.User::LeaveIfError() tests an integer parameter passed into itand causes a leave (using the integer value as a leave code) if the value isless than zero, for example, one of the KErrXXX error constants definedin e32std.h.
User::LeaveIfError() is useful for turning a nonleaving function which returns a standard Symbian OS error into one thatleaves 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 ishardcoded to be KErrNoMemory which makes it, in effect, the same ascalling User::Leave(KErrNoMemory).User::LeaveIfNull() takes a pointer value and leaves withKErrNoMemory if it is NULL. It can sometimes be useful, for example,to enclose a call to a non-leaving function which allocates memory andreturns a pointer to that memory or NULL if it is unsuccessful.The following example shows four possible leaves:TInt UseClanger(CClanger* aClanger);CClanger* InitializeClangerL(){CClanger* clanger = new (ELeave) CClanger();CleanupStack::PushL(clanger);clanger->InitializeL();User::LeaveIfError(UseClanger(clanger));CleanupStack::Pop(clanger);return (clanger);}// Forward declaration////////(1)(2)(3)(4)Leaves if OOMSee Chapter 3May leaveLeaves on errorThe L suffix is not checked during compilation so occasionally youmay forget to append L to a function name, or may later add code toa previously non-leaving function which may then cause it to leave.Symbian OS provides a helpful tool, LeaveScan, that checks code forincorrectly-named leaving functions.
It is described in more detail inSection 2.6.If a function may leave, its name must be suffixed with ”L”.16LEAVES: SYMBIAN OS EXCEPTIONS2.2 Heap Allocation Using new (ELeave)Let’s take a closer look at the use of new (ELeave) to allocate an objecton the heap. This overload leaves if the memory is unavailable, and thusallows the returned pointer to be used without a further test that theallocation 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 anadditional 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() toallocate an object on the heap you are invoking the new operator. Thisfirst allocates the memory required for the object by calling operatornew (yes, the naming scheme is incredibly confusing), passing in the sizeof the memory required.
It then calls the constructor to initialize an objectin that memory. This code is generated by the compiler, because it’s notpossible to call a constructor directly – which of course means that if youwant an object constructed on the heap, you must allocate it, via the newoperator, through a call to new().Symbian OS has overloaded the global operator new to take aTLeave parameter in addition to the size parameter provided implicitlyby new(). The TLeave parameter is ignored by operator new andis only used to differentiate this form of operator new from the nonleaving version.
The Symbian OS overload calls a heap allocation functionthat leaves if there is insufficient heap memory available:// From e32std.henum TLeave {ELeave};CONSTRUCTORS AND DESTRUCTORS17...inline TAny* operator new(TUint aSize, TLeave);// e32std.inlinline TAny* operator new(TUint aSize, TLeave){return User::AllocL(aSize);}Symbian OS has overloaded the global operator new to take aTLeave parameter.
This overload leaves if memory is unavailableon the heap.If a leaving function which allocates an object doesn’t leave,the allocation was successful and there is no need to check theresult further.2.3 Constructors and DestructorsBefore moving on to talk further about how to call leaving functions,let’s consider which functions should not leave. Quite simply, neither aconstructor nor a destructor should leave, since doing so would potentiallyleak memory and place the object upon which it is invoked in anindeterminate state.Chapter 4 will discuss this in more detail, but essentially, if a constructor can fail, say, through lack of the resources necessary to create orinitialize the object, you must remove the code that may leave from theconstructor and use the two-phase construction idiom instead.Likewise, a leave should not occur in a destructor or in cleanupcode.
One particular reason for this is that a destructor could itself becalled as part of cleanup following a leave and a further leave at thispoint would be undesirable, if nothing else because it would mask theinitial reason for the leave. More obviously, a leave part-way through adestructor will leave the object destruction incomplete which may leakits resources.If a destructor must call a leaving function, it may potentially betrapped and the leave code discarded, although this causes problemswhen testing out of memory leaves using the debug macros described inChapter 17. Generally, it’s preferable to have a separate leaving function,which can be called before destruction, to perform actions that may failand provide the caller an opportunity to deal with the problem beforefinally destroying the object.
Typically these would be functions such asCommitL() or FreeResourceL().18LEAVES: SYMBIAN OS EXCEPTIONSConstructors and destructors must not leave.2.4 Working with Leaving FunctionsLet’s look at the practicalities of working with leaves.
Below is anexample of a call to a leaving function. You’ll notice that there is noneed to check that ironChicken is initialized before using it, sinceCTestClass::NewL() would have left if any failure had occurred.void FunctionMayLeaveL(){// Allocates ironChicken on the heapCTestClass* ironChicken = CTestClass::NewL();// If NewL() didn’t leave, ironChicken was allocated successfullyironChicken->FunctionDoesNotLeave();delete ironChicken;}If the CTestClass::NewL() function leaves for some reason, itis the responsibility of that function to release any memory alreadyallocated as part of the function.
If successful, the function allocates,initializes and returns a heap-based object (NewL() functions and twophase construction are discussed in more detail in Chapter 4). In thecode above, a call to a non-leaving function follows, but consider theimplications if a leaving function was called instead. For example:void UnsafeFunctionL(){// Allocates test on the heapCTestClass* test = CTestClass::NewL();test->FunctionMayLeaveL(); // Unsafe – a potential memory leak!delete test;}This is unsafe. Memory is allocated on the heap in the call to CTestClass::NewL(), but the following function may leave. Should thisoccur test will not be deallocated; consequently the function has thepotential to leak memory. In a scenario such as this, you should push theheap object onto the cleanup stack, which will delete it should a leaveoccur.
The cleanup stack is described more fully in Chapter 3.While heap variables referred to only by local variables may beorphaned in this way, member variables will not suffer a similar fateWORKING WITH LEAVING FUNCTIONS19(unless their destructor neglects to delete them when it is called at somelater point). Thus the following code is safe:void CTestClass::SafeFunctionL(){iMember = CClangerClass::NewL(); // Allocates a heap memberFunctionMayLeaveL();// Safe}Note that the CTestClass object (pointed to by ”this” in CTestClass::SafeFunctionL()) is not deleted in the event of a leave.The heap-based iMember is stored safely as a pointer member variable,to be deleted at a later stage with the rest of the object, through theclass destructor.I’ve shown that you must prevent leaks from the potential orphaningof heap-based local variables, but what about cleanup of stack variablesif a leave occurs? The leave mechanism simply deallocates objects onthe stack – it does not call any destructors they have defined as it doesso, unlike a C++ throw.
Stack objects that own a resource which mustbe deallocated, or otherwise ”released” as part of destruction, would leakthat resource in the event of a leave. Classes which are intended to beused on the stack must not need a destructor.This is the reason why Symbian OS has a class naming convention which clearly defines the allowed usage of a class (described inChapter 1). The only classes which may be instantiated and used safelyon the stack are T classes, which the Symbian OS naming conventiondictates must not have a destructor, and R classes, which do not have adestructor but use Close(), or a similar method, to free the associatedresource. The cleanup stack must be used to ensure that this method iscalled in the event of a leave – I’ll discuss how to do so in the next chapter.class TMyClass{public:TMyClass(TInt aValue);private:TInt iValue;};void AnotherSafeFunctionL(){TInt localInteger = 1; // Leave-safe (built-in type)FunctionMayLeaveL(localInteger);TMyClass localObject(localInteger); // Leave-safe objectAnotherPotentialLeaverL(localObject);}Let’s consider what happens if you happen to have a local variable, an object of a T class, on the heap.