Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 8
Текст из файла (страница 8)
In a leaving function, you20LEAVES: SYMBIAN OS EXCEPTIONSstill need to protect the heap memory from being orphaned by a leavebut the object itself has no destructor. You’ll recall that I mentionedearlier that the cleanup stack performs both destruction and deallocation upon the objects in its care in the event of a leave. Well, that’strue for objects of class types which have destructors, but for T classobjects, it simply deallocates the memory. There’s more about this inChapter 3.void AnotherFunctionL(){TMyClass* localObject = new (ELeave) TMyClass();// Make localObject leave-safe using the cleanup stackCleanupStack::PushL(localObject);AnotherPotentialLeaverL(localObject);CleanupStack::PopAndDestroy(localObject);}2.5 Trapping a Leave Using TRAP and TRAPDSymbian OS provides two macros, TRAP and TRAPD, to trap a leave.The macros differ only in that TRAPD declares a variable in which theleave error code is returned, while the program code itself must declarea variable before calling TRAP.
Thus the following statement:TRAPD(result, MayLeaveL());if (KErrNone!=result) // See footnote 2{// Handle error}2Throughout this text, you’ll notice that I prefer to use ”back to front” comparisons inmy if statements to prevent accidentally typing only a single =, which is valid C++ but isn’tat all what is intended. Take the following example of the unforeseen consequences thatcan arise from this bug, which is often difficult to spot unless you have a helpful compilerthat warns you about it.TInt ContrivedFunction(){ // The bug in this function means it will always return 0for (TInt index = 0; index < KContrivedValue; index++){TInt calculatedValue = DoSomeComplexProcessing(index);// This assignment always returns trueif (calculatedValue=KAnticipatedResult)return (index);}return (KErrNotFound);}However, not everybody likes this style of coding.
If you prefer not to use this technique,it pays to compile with a high warning level and pay attention to any resulting warnings.TRAPPING A LEAVE USING TRAP AND TRAPD21is equivalent to:TInt result;TRAP(result, MayLeaveL());if (KErrNone!=result){// Handle error}...You should beware of nesting TRAPD macros and using the samevariable name, as in the following snippet:TRAPD(result, MayLeaveL())if (KErrNone==result){TRAPD(result, MayAlsoLeaveL())}...User::LeaveIfError(result);In the example, two TInt result variables are declared, one for eachTRAPD statement. The scope of the second result macro is bounded bythe curly brackets that enclose it. Thus any leave code assigned to thesecond result variable, from the call to MayAlsoLeaveL(), is discardedon exiting the bounding brackets.
The User::LeaveIfError() callthus only tests the leave code from the MayLeaveL() call, which isunlikely to be what the code intended. To ensure both values are tested,the second TRAPD should be replaced with a TRAP – thus reusing theTInt result declared by the initial TRAPD.If a leave occurs inside the MayLeaveL() function, which is executed inside the harness, the program control will return immediatelyto the trap harness macro. The variable result will contain theerror code associated with the leave (i.e. that passed as a parameterto the User::Leave() system function) or will be KErrNone if noleave occurred.Any functions called by MayLeaveL() are executed within the trapharness, and so on recursively, and any leave that occurs during theexecution of MayLeaveL() is trapped, returning the error code intoresult.
Alternatively, TRAP macros can be nested to catch and handleleaves at different levels of the code, where they can best be dealt with.I’ll discuss the runtime cost of using trap harnesses shortly, but if you findyourself using the TRAP macros several times in one function, or nesting aseries of them, you may want to consider whether you can omit trappingall the leaving functions except at the top level, or change the layout ofthe code.For example, there may be a good reason why the following functionmust not leave but needs to call a number of functions which may leave.22LEAVES: SYMBIAN OS EXCEPTIONSAt first sight, it might seem straightforward enough simply to put each callin a trap harness.TInt MyNonLeavingFunction(){TRAPD(result, FunctionMayLeaveL());if (KErrNone==result)TRAP(result,AnotherFunctionWhichMayLeaveL());if (KErrNone==result)TRAP(PotentialLeaverL());// Handle any error if necessaryreturn (result);}However, each TRAP has an impact in terms of executable sizeand execution speed.
Both entry to and exit of a TRAP macro resultin kernel executive calls3 (TTrap::Trap() and TTrap::UnTrap())being made. In addition, a struct is allocated at runtime to hold thecurrent contents of the stack in order to return to that state if unwindingproves necessary in the event of a leave. Of course, use of the macro itselfwill also create additional inlined code, though it is probably insignificantin comparison. The combination of these factors does make a TRAP quitean expensive way of managing a leave. You should attempt to minimizethe number of TRAPs you use in your code, where possible, either byallowing the leave to propagate to higher-level code or by making anadjustment similar to the following:MyNonLeavingFunction(){TRAPD(result, MyLeavingFunctionL());// Handle any error if necessaryreturn (result);}void MyLeavingFunctionL(){FunctionMayLeaveL();AnotherFunctionWhichMayLeaveL();PotentialLeaverL();}Of course, code is rarely as trivial as this example and you shouldbeware of losing relevant error information.
If a number of calls topotential leaving functions are packaged together as above and a leave3A kernel executive call is made by user-side code to allow it to enter processorprivileged mode in order to access kernel resources in a controlled manner. Control isswitched to the kernel executive, and the processor is switched to supervisor mode, withinthe context of the calling thread.TRAPPING A LEAVE USING TRAP AND TRAPD23code is returned from a call to the package of functions, it will not beclear which function left.Every program (even a simple ”hello world” application) must have atleast one TRAP, if only at the topmost level, to catch any leaves that arenot trapped elsewhere.
If you are an application writer you don’t needto worry about this, though, because the application framework providesa TRAP.When testing any code that may leave, you should test both paths ofexecution, that is, for a successful call and for a call that leaves as a resultof each of the exceptional conditions you expect to handle, for example,low memory conditions, failure to write to a file, etc. The Symbian OSmacros to simulate such conditions are described in Chapter 17.I mentioned earlier that, under normal circumstances, you shouldn’timplement functions which both return an error and have the potentialto leave. For example, consider a function, OpenFileObjectL(), thatinstantiates an object (CFileObject) which must be initialized with anopen file handle.
The implementation of this object may be in a separatelibrary, the source code for which may not be available to the writer ofthis code.TInt OpenFileObjectL(CFileObject*& aFileObject){// File server session initialization is omitted for clarity... // (handle is leave-safe)RFile file;TInt error = file.Open(...);if (KErrNone==error){CleanupClosePushL(file); // Makes handle leave-safe// Propagates leaves from CFileObjectaFileObject = CFileObject::NewL(file);CleanupStack::Pop(&file); // Owned by aFileObject now}return error; // Returns any error from RFile::Open()}When a caller comes to use OpenFileObjectL(), they’ll find thatthis function is more complex than it needs to be.
At best, they may findthemselves writing code something like the following:void ClientFunctionL(){// Pass all errors up to be handled by a higher-level TRAP harnessCFileObject* fileObject = NULL;TInt errorCode=OpenFileObjectL();if (KErrNone!=errorCode){User::Leave(errorCode);}...}24LEAVES: SYMBIAN OS EXCEPTIONSOr they may use a TRAP to catch any leaves and return them as errors:TInt ClientFunction(){CFileObject* fileObject = NULL;TInt errorCode;TRAPD(r, errorCode=OpenFileObjectL());if (KErrNone!=r)return (r);if (KErrNone!=errorCode)return (errorCode);...}Neither of these options is very attractive. Furthermore, if the clientfunction can actually handle some of the errors at that point in the code, itbecomes even messier. Should it differentiate between the two methods ofpropagating an error? And if so, how? Are the leaves in some way differentto the errors returned? Could the same error sometimes be returned as anerror value (say KErrNotFound from RFile::Open()) and sometimesas a leave code (say a leave from CFileObject::NewL() whichattempts to read the contents of the file and finds it is missing some vitalconfiguration data)?In effect, this approach requires you to document the function clearlyto allow callers to use it correctly and handle errors or exceptions as theychoose.
Maybe you can guarantee that you, or whoever takes responsibility for maintaining this function, keeps the documentation up to date.But you are returning an error directly from one, separate, component(class RFile) and a leave code from another (class CFileObject). Evenif you know exactly what errors and leave codes each will use and areprepared to document them for callers of OpenFileObjectL(), youcannot guarantee that their error handling will not change.It is preferable to restrict the code either to leaving or to returning an error. First, consider an implementation that leaves under allcircumstances (notice that I’ve changed the signature to return theCFileObject rather than pass it as a reference-to-pointer parameter).CFileObject* LeavingExampleL(){RFile file;User::LeaveIfError(file.Open(...));return (CFileObject::NewL(file));}The implementation of the function is certainly smaller and less complex.
If the calling code can handle some errors, then it can TRAP themethod and switch on the error value returned in order to distinguishbetween the exceptional case and the non-exceptional case.TRAPPING A LEAVE USING TRAP AND TRAPD25void ClientFunctionL(){CFileObject* fileObject = NULL;TRAPD(r, fileObject = LeavingExampleL());switch (r){case (KErrNoMemory):...