Wiley.Symbian.OS.C.plus.plus.for.Mobile.Phones.Aug.2007 (779890), страница 19
Текст из файла (страница 19)
As SymbianOS doesn’t use C++ exceptions, it needs its own mechanism to ensurethat this does not happen. This mechanism is the cleanup stack (seeSection 4.4 below).You don’t often need to code your own traps, partly because the Symbian OS framework provides a default TRAP macro for you, and partlybecause routine cleanup is handled by the cleanup stack. Sometimes,though, you’ll need to perform a recovery action or some non-routinecleanup. Or you’ll need to code a non-leaving function, such as Draw(),that allocates resources and completes successfully if possible, but otherwise traps any leaves and handles them internally.
In these cases, youmust code your own trap.While Draw() functions do not normally need to allocate anyresources, some are rather complicated and it may be appropriate tocode them in such a way that they do make allocations. In this case, youhave to hide the fact from the Symbian OS framework by trapping anyfailures yourself.virtual void Draw(const TRect& aRect) const{TRAPD(error, MyPrivateDrawL(aRect));}In this case, we choose to keep quiet if MyPrivateDrawL() failed.The failing draw code should take some graceful action, such as drawingin less detail, or blanking the entire rectangle, or not updating the previousdisplay.Don’t use traps when you don’t have to.
Here’s a particularly uselessexample:TRAPD(error, FooL());if(error)User::Leave(error);The net effect of this code is precisely equivalent to:FooL();However, it’s more source code, more object code, and more processing time whether or not FooL() actually does leave.76OBJECTS – MEMORY MANAGEMENT, CLEANUP AND ERROR HANDLING4.4 The Cleanup StackThe cleanup stack addresses the problem of cleaning up objects whichhave been allocated on the heap, but whose owning pointer is anautomatic variable. If the function that has allocated the object leaves,then the object needs to be cleaned up.In the following example, the UseL() function allocates memory andmight leave.case ECmd3:{CX* x = new (ELeave) CX;x->UseL();delete x;}...void CX::UseL(){TInt* pi = new (ELeave) TInt;delete pi;}This code can go wrong in two places.
First, the allocation of CX mightfail – if so, the code leaves immediately with no harm done.If the allocation of the TInt fails in UseL(), then UseL() leaves, andthe CX object, which is pointed to only by the automatic variable x inthe case statement, can never be deleted. The memory is orphaned – amemory leak has occurred.Using the Cleanup StackAfter the line CX* x = new (ELeave) CX; has been executed, theautomatic x points to a cell on the heap.
After the leave, the stack framecontaining x is abandoned without deleting x. That means that the CXobject is on the heap, but no pointer can reach it, and it will never bedestroyed.The solution is to make use of the cleanup stack. As its name implies,the cleanup stack is a stack of entries representing objects that are to becleaned up if a leave occurs. It is accessed through the CleanupStackclass.
The class has push- and pop-type functions that allow you toput items onto the stack and remove them. In a console applicationthe cleanup stack must be explicitly provided, but in a standard GUIapplication it is provided by the UI Framework.C++’s native exception handling addresses the problem of automaticson the stack by calling their destructors explicitly, so that a separatecleanup stack isn’t needed. As discussed in Chapter 2, C++ exceptionTHE CLEANUP STACK77handling was not standardized or well supported when Symbian OS wasdesigned, so it wasn’t an option to use it.Stack-frame object destructors are not called if a leave happens, inother words objects on the stack frame are not cleaned up. Onlyobjects on the cleanup stack are cleaned up.Here’s how we should have coded the previous example:case ECmd3:{CX* x = new (ELeave) CX;CleanupStack::PushL(x);x->UseL();CleanupStack::PopAndDestroy(x);}The cleanup stack class, CleanupStack, is defined in e32base.h.With these changes in place, the sequence of events is:• Immediately after we have allocated the CX and stored its pointer inx, we also push a copy of this pointer onto the cleanup stack.• We then call UseL().• If this doesn’t fail (and leave), our code pops the pointer from thecleanup stack and deletes the object.
We could have used two linesof code for this (CleanupStack::Pop(), followed by delete x),but this is such a common pattern that the cleanup stack provides asingle function to do both.• If UseL() does fail, then as part of leave processing, all objects onthe cleanup stack in the current trap harness, including our CX object,are popped and destroyed.We could have done this without the aid of the cleanup stack by usingcode like this:case ECmd3:{CX* x = new (ELeave) CX;TRAPD(error, x->UseL());if(error){delete x;User::Leave(error);}delete x;}78OBJECTS – MEMORY MANAGEMENT, CLEANUP AND ERROR HANDLINGHowever, this is much less elegant.
The cleanup stack works particularly well for long sequences of operations, such as:case ECmd3:{CX* x = new (ELeave) CX;CleanupStack::PushL(x);x->UseL();x->UseL();x->UseL();CleanupStack::PopAndDestroy();}Any one of the calls to UseL() may fail, and it would look very messyif we had to surround every L function with a trap harness just to addresscleanup. It would also reduce efficiency and increase the size of the codeto unacceptable levels.The cleanup stack is a stack like any other, and you can add more thanone item to it. So for example, we can have:case ECmd3:{X* x1 = new (ELeave) CX;CleanupStack::PushL(x1);CX* x2 = new (ELeave) CX;CleanupStack::PushL(x2);x1->UseL();CleanupStack::PopAndDestroy(2);}The call to PopAndDestroy(2) causes the last two items to beremoved from the stack and destroyed. When you do this, you must becareful not to remove more items from the cleanup stack than you putonto it, otherwise your program will panic.Don’t Use the Cleanup Stack UnnecessarilyA common mistake when using the cleanup stack is to be overenthusiastic,putting all possible pointers to heap objects onto the cleanup stack.
Thisis wrong. You need to put a pointer to an object onto the cleanup stackonly to prevent an object’s destructor from being bypassed. If the object’sdestructor is going to be called anyway, then you must not use thecleanup stack.If an object is a member variable of another class, it will be destroyedby the class’s destructor, so you should never push a member variableto the cleanup stack.THE CLEANUP STACK79According to Symbian OS coding conventions, member variables areindicated by an i prefix, so code such as the following is always wrong.CleanupStack::PushL(iMember);This is likely to result in a double deletion, once from the cleanup stackand once from the class’s destructor. Following the coding conventionsmakes it very easy to spot and prevent coding mistakes such as the oneabove.Another way of thinking about this is that once a pointer to a heapobject has been copied into a member of some existing class instance,then you no longer need that pointer on the cleanup stack.
This isbecause the class destructor takes responsibility for the object’s destruction (assuming, of course, that the destructor is correctly coded to do this).What if CleanupStack::PushL() Itself Fails?Pushing to the cleanup stack potentially allocates memory, and thereforemay itself fail! You don’t have to worry about this, because such a failurewill be handled properly.
But for reassurance, here’s what happens underthe covers.Symbian OS addresses this possibility by always keeping at least onespare slot on the cleanup stack. When you do a PushL(), the object youare pushing is first placed onto the cleanup stack (which is guaranteed towork, because there was a spare slot). Then a new slot is allocated.
If thatfails, the object you just pushed is popped and destroyed.The cleanup stack actually allocates more than one slot at once, anddoesn’t throw away existing slots when the contents are popped. Sopushing and popping from the cleanup stack are very efficient operations.Since the cleanup stack is used to hold temporary objects, or objectswhose pointers have not yet been stored as member pointers in theirparent object during the parent object’s construction, the number ofcleanup stack slots needed by a practical program is not large; more thanten would be very rare.
So the cleanup stack itself is very unlikely to be acontributor to out-of-memory errors.CBase and the Cleanup StackIn the earlier examples, when we push x to the cleanup stack, weare invoking the function CleanupStack::PushL(CBase* aPtr),because CX is derived from CBase. When a subsequent PopAndDestroy() happens, this function can only call the destructor of aCBase-derived object.As mentioned in Section 4.2, CBase has a virtual destructor.
Thismeans that any object derived from CBase can be pushed onto the80OBJECTS – MEMORY MANAGEMENT, CLEANUP AND ERROR HANDLINGcleanup stack and, when it is popped and destroyed, the correct destructorfunction is called.The cleanup stack and C++ destructors make it very easy for aprogrammer to handle cleanup. Use the cleanup stack for heap objectspointed to only by C++ automatics; use the destructor for objects pointedto by member variables. You very rarely need to use TRAP(). Theresulting code is easy to write, compact, and efficient.R Classes on the Cleanup StackSometimes you need to create and use an R-class object as an automaticvariable rather than as a member of a C class.