Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 14
Текст из файла (страница 14)
It is under these circumstances that two-phaseconstruction is useful.50TWO-PHASE CONSTRUCTIONWhen writing a new class you should break your construction codeinto two parts, or phases:1.A basic constructor which cannot leave.It is this constructor which is called by the new operator. It implicitlycalls base-class constructors and may also invoke functions thatcannot leave and/or initialize member variables with default valuesor those supplied as arguments to the constructor.2.A class method (typically called ConstructL()).This method may be called separately once the object, allocatedand constructed by the new operator, has been pushed onto thecleanup stack; it will complete construction of the object and maysafely perform operations that may leave.
If a leave does occur, thecleanup stack calls the destructor to free any resources which havebeen successfully allocated and destroys the memory allocated forthe object itself.For example:class CExample : public CBase{public:CExample();// Guaranteed not to leave∼CExample();// Must cope with partially constructed objectsvoid ConstructL(); // Second phase construction code - may leave...};The simplest implementation may expect clients to call the secondphase construction function themselves, but a class cannot rely on itsobjects being fully constructed unless it makes the call internally itself. Acaller may forget to call the ConstructL() method after instantiatingthe object and, at the least, will find it a burden since it’s not a standardC++ requirement.
For this reason, it is preferable to make the call tothe second phase construction function within the class itself. Obviously,the code cannot do this from within the simple constructor since thistakes it back to the problem of having implemented a constructor whichmay leave.A commonly used pattern in Symbian OS code is to provide a staticfunction which wraps both phases of construction, providing a simpleand easily identifiable means to instantiate objects of a class on the heap.The function is typically called NewL(), though a NewLC() function isoften provided too, which is identical except that it leaves the constructedobject on the cleanup stack for convenience.11In fact, NewLC() is such a commonly used idiom when working with Symbian OSthat a very useful, independent Symbian developer website (www.NewLC.com) exists,providing news, tutorials and support forums.TWO-PHASE CONSTRUCTION51class CExample : public CBase{public:static CExample* NewL();static CExample* NewLC();∼CExample();// Must cope with partially constructed objectsprivate:CExample();// Guaranteed not to leavevoid ConstructL(); // Second phase construction code, may leave...};Note that the NewL() function is static, so you can call it without firsthaving an existing instance of the class.
The non-leaving constructors andsecond-phase ConstructL() functions have been made private2 so acaller cannot instantiate objects of the class except through NewL().This prevents all of the following erroneous constructions:CExample froglet; // BAD! C classes should not be created on the stackCExample* myFroglet = new CExample(); // Caller must test for successif (NULL!=myFroglet){myFroglet->Hop(); // ConstructL() for myFroglet has not been called}CExample* frogletPtr = new (ELeave) CExample();frogletPtr->Hop(); // ConstructL() wasn’t called, frogletPtr may not be// fully constructedTypical implementations of NewL() and NewLC() may be as follows:CExample* CExample::NewLC(){CExample* me = new (ELeave) CExample(); // First phase constructionCleanupStack::PushL(me);me->ConstructL(); // Second phase constructionreturn (me);}CExample* CExample::NewL(){CExample* me = CExample::NewLC();CleanupStack::Pop(me);return (me);}2If you intend your class to be subclassed, you should make the default constructorprotected rather than private so the compiler may construct the deriving classes.
TheConstructL() method should be private (or protected if it is to be called by derivedclasses) to prevent clients of the class from mistakenly calling it on an object which hasalready been fully constructed.52TWO-PHASE CONSTRUCTIONNote that the NewL() function is implemented in terms of theNewLC() function rather than the other way around (which wouldbe slightly less efficient since this would make an extra PushL() call onthe cleanup stack).Each function returns a fully constructed object, or will leave eitherif there is insufficient memory to allocate the object (that is, if thespecial Symbian OS overload of operator new leaves) or if the secondphase ConstructL() function leaves.
If second phase constructionfails, the cleanup stack ensures both that the partially constructed objectis destroyed and that the memory it occupies is returned to the heap.The NewL() and NewLC() functions may, of course, take parameterswith which to initialize the object. These may be passed to the simple constructor in the first phase or the second-phase ConstructL()function, or both.If your class derives from a base class which also implements ConstructL(), you will need to ensure that this is also called, if necessary, when objects of your class are constructed (C++ will ensure thatthe simple first-phase constructors of your base classes are called).
Youshould call the ConstructL() method of any base class explicitly (usingthe scope operator) in your own ConstructL() method, to ensure thebase class object is fully constructed, before proceeding to initialize yourderived object.It is with class inheritance in mind that we can answer the followingquestion: If it is possible to PushL() a partially constructed object ontothe cleanup stack in a NewL() function, why not do so at the beginningof a standard constructor (thus allowing it to leave), calling Pop() whenconstruction is complete? At first sight, this may be tempting, since thesingle-phase construction I described as unsafe at the beginning of thechapter would then be leave-safe, as long as the object was pushedonto the cleanup stack before any leaving operations were called inthe constructor.
However, if the class is to be used as a base class,the constructor of any class derived from it will incur one PushL()(and corresponding Pop()) in the constructor called at each level inthe inheritance hierarchy, rather than a single cleanup stack operation inthe NewL() function.
In addition, from a cosmetic point of view, a C++constructor cannot be marked with a suffixed L to indicate its potentialto leave unless the class is itself named as such.Before closing this chapter, it is worth noting that, when implementingthe standard Symbian OS two-phase construction idiom, you shouldconsider the destructor code carefully. Remember that a destructor mustbe coded to release all the resources that an object owns. However,the destructor may be called to cleanup partially constructed objectsif a leave occurs in the second-phase ConstructL() function. Thedestructor code cannot assume that the object is fully initialized and youshould beware of calling functions on pointers which may not yet be setSUMMARY53to point to valid objects. Of course, the memory for a CBase-derivedobject is guaranteed to be set to binary zeroes on first construction (asdescribed in Chapter 1).
It is safe for a destructor to call delete on aNULL pointer, but you should beware of attempting to free other resourceswithout checking whether the handle or pointer which refers to them isvalid, for example:CExample::∼CExample(){if (iMyAllocatedMember){iMyAllocatedMember->DoSomeCleanupPreDestruction();delete iMyAllocatedMember;}}On Symbian OS, a C++ constructor should never leave, sinceany memory allocated for the object (and any memory the constructor may already have allocated) would be orphaned by theleave. Instead, construction code should be broken into two phaseswithin a public static member function, typically called NewL() orNewLC().4.1 SummaryA constructor should never be able to leave, because if it is calledand leaves when an object is instantiated on the heap that object willbe orphaned, causing a memory leak. For this reason, two-phase construction is used extensively in Symbian OS code for CBase-derivedclasses.