Wiley.Symbian.OS.C.plus.plus.for.Mobile.Phones.Aug.2007 (779890), страница 20
Текст из файла (страница 20)
In this case, you need tobe able to push it to the cleanup stack. There are two options available:• use CleanupClosePushL(), CleanupReleasePushL() orCleanupDeletePushL()• do it directly: make a TCleanupItem consisting of a pointer to theR-class object, and a static function that will close it.If you have an item with a Close() function, then CleanupClosePushL(), a global non-member function, will ensure that Close() iscalled when the item is popped and destroyed by the cleanup stack. C++templates are used for this, so Close() does not have to be virtual.The following code demonstrates how to use CleanupClosePushL():case ECmdDeleteFile:{RFs fs;CleanupClosePushL(fs);User::LeaveIfError(fs.Connect());User::LeaveIfError(fs.Delete(KTextFileName));CleanupStack::PopAndDestroy(&fs);}You just call CleanupClosePushL() after you’ve declared yourobject, and before you do anything that could leave.
You can then useCleanupStack::PopAndDestroy() to close the object when you’vefinished with it.CleanupClosePushL() pushes a TCleanupItem onto the cleanup stack. A TCleanupItem is a general-purpose object encapsulatingan object to be cleaned up and a static function that will close it. WhenCleanupStack::PopAndDestroy() is called, the cleanup operationis performed on that object. In this particular example, the object isfs and the cleanup operation is the function Close() that is calledon it.TWO-PHASE CONSTRUCTION81There are two other related functions:• CleanupReleasePushL() works like CleanupClosePushL()except that it calls Release() instead of Close() during objectcleanup.
You should use this when your object is a resource that youare holding (rather than a handle).• CleanupDeletePushL() is used for pushing heap-allocated,T-class objects onto the cleanup stack. In contrast with R-class objects,T-class objects do not normally have Close() or Release()member functions, but they do have a class destructor. Therefore, CleanupDeletePushL() is equivalent to the CleanupStack::PushL(TAny*) overload except that, CleanupDeletePushL() calls the class destructor. Another frequent use is when wehave a pointer to an M-class object that needs to be placed on thecleanup stack.You can also create your own TCleanupItem and push it onto thecleanup stack if the system-supplied cleanup functions are insufficient.The TCleanupItem class is defined in e32base.h, and contains apointer and a cleanup function. Anything pushed to the cleanup stack isactually stored in a cleanup item.
CleanupStack::PushL(CBase*),CleanupStack::PushL(TAny*) and CleanupClosePushL() simply create appropriate cleanup items and push them.4.5 Two-Phase ConstructionTwo-phase construction is a pattern for preventing memory leaks thatwould otherwise occur during the construction of complex objects. Ifa constructor allocates memory (e.g. for a member variable), and theallocation fails and leaves, the object being constructed is not yet on thecleanup stack, and is therefore orphaned.We need a mechanism for separating the safe parts of constructionfrom the unsafe parts, and delaying the unsafe parts until the object hasbeen pushed onto the cleanup stack.
This mechanism is the two-phaseconstruction pattern.Separating Safe from Unsafe ConstructionsThe following example has a class called CY, containing a membervariable which points to a CX. Using conventional C++ techniques, weallocate the CX from the CY constructor:class CY : public CBase{public:82OBJECTS – MEMORY MANAGEMENT, CLEANUP AND ERROR HANDLINGCY();∼CY();public:CX* iX;};CY::CY(){iX = new (ELeave) CX;}CY::∼CY(){delete iX;}We can then make a call to CX::UseL(), using cleanup-friendlycode as follows:case ECmd4:{CY* y = new (ELeave) CY;CleanupStack::PushL(y);y->iX->UseL();CleanupStack::PopAndDestroy();}In this example we have used C++ constructors in the usual way, andused the cleanup stack properly. Even so, this code isn’t cleanup-safe.
Itmakes three allocations as it runs through.• The command handler allocates the CY: if this fails, everything leavesand there’s no problem.• The CY constructor allocates the CX: if this fails, the code leaves, butthere is no CY on the cleanup stack, and the CY object is orphaned.• CX::UseL() allocates a TInt: by this time, the CY is on the cleanupstack, and the CX will be looked after by CY’s destructor, so if thisallocation fails, everything gets cleaned up nicely.The trouble here is that the C++ constructor is called at a time whenno pointer to the object itself is accessible to the program. This code:CY* y = new (ELeave) CY;is effectively expanded by C++ to:CY* y;CY* temp = User::AllocL(sizeof(CY));temp->CY::CY();y = temp;// Allocate memory// C++ constructorTWO-PHASE CONSTRUCTION83The problem is that we get no opportunity to push the CY onto thecleanup stack between allocating the memory for the CY, and the C++constructor, which might leave.
And there’s nothing we can do about this.It is a fundamental rule of Symbian OS programming that no C++constructor may contain any functions that can leave.To get around this, we need to provide a separate function to do anyinitialization that might leave. We call this function the second-phaseconstructor, and the Symbian OS convention is to call it ConstructL().The second-phase constructor must only be called after the object underconstruction has been pushed onto the cleanup stack.Class CZ is like CY but uses a second-phase constructor:class CZ : public CBase{public:static CZ* NewL();static CZ* NewLC();void ConstructL();∼CZ();public:CX* iX;};void CZ::ConstructL(){iX = new (ELeave) CX;}CZ::ConstructL() performs the same task as CY::CY(), so theleaving function new (ELeave) is now in the second-phase constructorConstructL().
This is now cleanup-safe.Wrapping Up ConstructL() in NewL() and NewLC()Working with the two-phase constructor pattern can be inconvenient,because the user of the class has to remember to call the second-phaseconstructor explicitly.To make this easier and more transparent, we use the NewL() andNewLC() patterns. The CZ class has a static NewLC() function that’scoded as follows:CZ* CZ::NewLC(){CZ* self = new (ELeave) CZ;CleanupStack::PushL(self);self->ConstructL();return self;}84OBJECTS – MEMORY MANAGEMENT, CLEANUP AND ERROR HANDLINGBecause CZ::NewLC() is static, you can call it without any existinginstance of CZ. The function allocates a CZ with new (ELeave) and thenpushes it onto the cleanup stack so that the second-phase constructor cansafely be called.
If the second-phase constructor fails, then the object ispopped and destroyed by the rules of the cleanup stack. If all goes well,the object is left on the cleanup stack – that’s what the C in NewLC()stands for. NewLC() is useful if, on returning, we want to store thereference to the new CZ in an automatic variable.Often, however, we don’t want to keep the new CZ on the cleanupstack, and we use the NewL() static function. This can be coded asfollows:CZ* CZ::NewL(){CZ* self = new (ELeave) CZ;CleanupStack::PushL(self);self->ConstructL();CleanupStack::Pop();return self;}This is exactly the same as NewLC(), except that we pop the CZ objectoff the cleanup stack after the second phase constructor has completedsuccessfully.The two implementations are almost the same, and NewL() is commonly implemented in terms of NewLC(), like this:CZ* CZ::NewL(){CZ* self = CZ::NewLC();CleanupStack::Pop();return self;}One interesting thing to note in the NewL() implementation is that wehave popped an item from the cleanup stack without destroying it.
Allwe’re doing here is putting a pointer onto the cleanup stack only for aslong as there is a risk of leaving. The implication is that on return from theNewL(), ownership of the object is passed back to the caller of NewL(),who then has to take responsibility for it.CZ::NewLC() and CZ::NewL() are known as factory functions –static functions that act as a replacement for normal constructors.It is good practice to make the class constructors private, in order toensure that users always use the NewL() and NewLC() functions, thusensuring that their code is always cleanup-safe.SUMMARY85SummaryIn this chapter we have explained the principles of memory managementand cleanup, but we could summarize all of it in a single rule:Whenever you see something that might leave, think about whathappens (a) when it doesn’t leave, and (b) when it does.In practice it’s better to operate with a few safe patterns, so below is amore detailed summary of the rules we’ve seen.• Always delete objects your class owns, from the class destructor.• Don’t delete objects that you don’t own (those that you merely use).• Don’t allocate twice (this will cause a memory leak).• Don’t delete twice (this will corrupt the heap).• When you delete outside of the destructor, immediately set the pointerto zero.• When you are reallocating, you must use the sequence ‘delete, setpointer to zero, allocate’, just in case the allocation fails.• Use new (ELeave) rather than plain new.• Use L on the end of the name of any function that might leave.• Use traps only where you need to – for instance, when a functioncan’t leave and must handle errors privately.• Push an object to the cleanup stack if (a) that object is only referred toby an automatic pointer, and (b) you are going to call a function thatmight leave during that object’s lifetime.• Never push a member variable to the cleanup stack – use the iMember naming convention to help you spot this error.• Give all heap-based classes a name beginning with C, and derivethem from CBase.