Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 66
Текст из файла (страница 66)
This approachguarantees the singularity of the object, in effect, enforcing it at compiletime, and also delays the construction until the object is needed, which18Rather confusingly, DLL code that used WSD did actually build and run on the Windowsemulator on versions of Symbian OS that didn’t support the use of WSD in DLLs.
It was onlywhen the same code was compiled for target hardware that the use of WSD was actuallyflagged as an error – a time-consuming irritant for those working primarily on the emulatorto build and debug in the early stages of a project. You can find out how to track downWSD if you’re not using it deliberately in FAQ 0329 on the Symbian Developer Network(developer.symbian.com/main/support/faqs ).350MAPPING WELL-KNOWN PATTERNS ONTO SYMBIAN OSis known as Lazy Allocation (see page 63). Using that pattern can beadvantageous if an object is ‘expensive’ to instantiate and may notbe used.
A good example is a logging class that is only used if an erroroccurs when the code is running. There is no point in initializing andholding open a resource such as a file server session if it is not neededwhen the code logic runs normally.Hazards of SingletonThis pattern is often embraced for its simplicity by those new to designpatterns, but it presents a number of potential pitfalls to the unwaryprogrammer, especially the cleanup and thread-safety of the singletonclass itself.For example, we’ve just discussed the construction of Singleton,but what about the other end of its lifetime? Note that the destructor isprivate19 to prevent callers of Instance() from deleting pInstance_accidentally or on purpose. However, this does raise some interestingquestions: When should the singleton instance destroy itself? How do wede-allocate memory and release resource handles? [Alexandrescu, 2001]is a good place to find out more and we’ll discuss it in the Solutionsection.Also, how do we ensure that the implementation is thread-safe if thesingleton instance needs to be accessed by more than one thread? Afterall, it’s intended to be used as a shared global resource, so it’s quitepossible that multiple threads could simultaneously attempt to create oraccess the singleton.
The classical implementation we examined earlieris not thread-safe, because race conditions are possible when separatethreads attempt to create the singleton. At worst, a memory leak arisesif the first request for the singleton by one thread is interrupted by thethread scheduler and another thread then runs and also makes a requestfor the singleton object.Here again is the code for Instance(), with line numbers to aid thediscussion:1 /*static*/ Singleton* Singleton::Instance()2{3if (!pInstance_)4{5pInstance_ = new Singleton;6}7return (pInstance_);8}Consider the following scenario: Thread A runs and calls Instance().The singleton pInstance_ has not yet been created, so the code runson to line 5, but it is then interrupted by the thread scheduler before19 Ifthe Singleton class is ever intended to be sub-classed, it should be made protected.SINGLETON351pInstance_ can be created.
Thread B starts executing, also callsInstance() and receives the same result as thread A when it teststhe pInstance_ pointer. Thread B runs to line 5, creates the singletoninstance and continues successfully. However, when Thread A resumes,it runs from line 5, and also instantiates a Singleton object, assigning itto pInstance_. Suddenly there are two Singleton objects when thereshould be just one, causing a memory leak because the one pInstance_pointer references only the latter object.When Singleton is used with multiple threads, race conditions clearlyneed to be avoided and we’ll discuss how to do this in the Solutionsection below. [Myers and Alexandrescu, 2004] provides an even moredetailed analysis.Should You Use Singleton?Be wary of just reaching for this pattern as there are a number of subtletiesthat can catch you out later. This pattern increases the coupling ofyour classes by making the singleton accessible ‘everywhere’, as well asmaking unit testing more difficult as a singleton carries its state with itas long as your program lasts.
There are a number of articles that discussthis in more depth but here are just a few:• Do we really need singletons? [Saumont, 2007]• Use your singletons wisely [Rainsberger, 2001]• Why singletons are evil [Densmore, 2004]SynopsisWe’ve covered quite a lot of ground already, so let’s summarize theproblems that we need to address in the solution:• Symbian-specific issues• The classic implementation of Singleton does not allow for potential out-of-memory conditions or two-phase construction. What isthe preferred approach in Symbian C++?• Singleton cannot be implemented using the classic solution, whichdepends on WSD, where WSD is not supported (in DLLs in Symbian OS v8.1a or earlier) or is not recommended (DLLs in SymbianOS v9).
How can you work around this?• General issues• When should a singleton instance destroy itself?• How should an implementation of Singleton cope with access bymultiple threads?352MAPPING WELL-KNOWN PATTERNS ONTO SYMBIAN OSExampleOn Symbian OS, the window server (WSERV) provides a low-level APIto the system’s user interface devices – screens, keyboard and pointer.The API is extensive and complex and would make most applicationdevelopment unnecessarily convoluted if used directly. For example, toreceive events caused by the end user interacting with a list box, youwould need to use a window server session, as well as Active Objects (seepage 133), to handle asynchronous events raised by WSERV in responseto input from the end user.The control framework (CONE) hides much of this complexity andprovides a simplified API that meets the requirements of most applications.
CONE encapsulates the use of Active Objects (see page 133)and simplifies the interaction with the Window Server by providing asingle, high-level class, CCoeEnv, for use by application programmers.CCoeEnv also provides simplified access to drawing functions, fonts,and resource files which are used by many applications. The class isalso responsible for creation of the cleanup stack for the application,for opening sessions to the window server, file server, screen device,application’s window group and a graphics context.Only one instance of CCoeEnv is needed per thread since it providesthread-specific information. However, CCoeEnv is intended for use bya potentially large number of different classes within an application,so the instance of CCoeEnv should be globally accessible within eachthread.SolutionDepending on the scope across which you require the singleton to beunique there are a number of different ways of working around theproblem as shown in Table 9.3.Some of the implementations discussed are accompanied by theirown issues.
For example, at the time of writing, Implementation Cshould be regarded as illustrative rather than recommended on Symbian OS.Implementation A: Classic Singleton in a Single ThreadConstraints on the Singleton• Must only be used within a single thread.• Can be used within an EXE on any version of Symbian OS or in a DLLon Symbian OS v9.x if absolutely necessary.• WSD may be used.SINGLETON353Table 9.3 Synopsis of SolutionsImplementationMaximum RequiredScope for the SingletonDetailsRequires WSD?AWithin a GUIapplication orother EXEs suchas consoleapplications orserversImplement normallywith only minormodification forSymbian C++.YesBWithin a DLL fora specific threadUse thread localstorage (TLS).NoCAcross multiplethreadsUse a threadsynchronizationmechanism toprevent raceconditions.YesDAcross multiplethreadsManage initializationand access to theTLS data as eachsecondary threadstarts running.NoEAcross multipleprocessesUse the Symbian OSclient–serverframework.NoDetailsOn Symbian OS, the implementation of Singleton must take into accountthe possibility of instantiation failure.
For example, a leave because thereis insufficient memory to allocate the singleton instance. One possibletechnique is to implement a standard NewL() factory function that willbe familiar to Symbian C++ developers:class CSingleton : public CBase{public:// To access the singleton instancestatic CSingleton& InstanceL();private: // The implementations of the following are omitted for clarityCSingleton();∼CSingleton();static CSingleton* NewL();void ConstructL();private:static CSingleton* iSingletonInstance; // WSD};354MAPPING WELL-KNOWN PATTERNS ONTO SYMBIAN OS/*static*/ CSingleton& CSingleton::InstanceL(){if (!iSingletonInstance){iSingletonInstance = CSingleton::NewL();}return (*iSingletonInstance);}Inside InstanceL(), the NewL() factory method is called to createthe instance.
The main issue is what to do if this fails? One option wouldbe to ignore any errors and pass back a pointer to the singleton that mightbe NULL. However, this puts the burden on clients to check for it beingNULL. They are likely to forget at some point and get a KERN-EXEC 3panic. Alternatively, we could panic directly if we fail to allocate thesingleton which at least tells us specifically what went wrong but stilltakes the whole thread down which is unlikely to be the desired outcome.The only reasonable approach is to use Escalate Errors (see page 32) andleave (see Figure 9.5). This means the client would have to deliberatelytrap the function and willfully ignore the error to get a panic. While thisis entirely possible, it won’t happen by accident.
It also gives the clientthe opportunity to try a less severe response to resolving the error thanterminating the thread.Figure 9.5 Structure of Implementation A (basic)Note: On most versions of Symbian OS, the tool chain disables theuse of WSD in DLLs by default. To enable WSD you must add theEPOCALLOWDLLDATA keyword to your MMP file. However, there aresome builds of Symbian OS (for example, Symbian OS v9.3 in S603rd Edition FP2) that allow WSD in DLLs without the need to usethe EPOCALLOWDLLDATA keyword.
In addition, the currently supportedversion of the GCC-E compiler has a defect such that DLLs with staticdata may cause a panic during loading. The issue, and how to workaround it, is discussed further in [Willee, Nov 2007].A variant of this approach is to give clients of the singleton morecontrol and allow them to instantiate the singleton instance separatelyfrom actually using it (see Figure 9.6), allowing them to choose when tohandle any possible errors that occur.SINGLETON355Figure 9.6 Structure of Implementation A (lifetime managed)A separate method can then be supplied to guarantee access to thesingleton instance once it has been instantiated.class CSingleton : public CBase{public:// To create the singleton instancestatic void CreateL();// To access the singleton instancestatic CSingleton& Instance();private: // The implementations of the following are omitted for claritystatic CSingleton* NewL();CSingleton();∼CSingleton();void ConstructL();private:static CSingleton* iSingletonInstance; // WSD};/*static*/ void CSingleton::CreateL(){// Flags up multiple calls in debug buildsASSERT(!iSingletonInstance);// Create the singleton if it doesn’t already existif (!iSingletonInstance){iSingletonInstance = CSingleton::NewL();}}/*static*/ CSingleton& CSingleton::Instance(){ASSERT(iSingletonInstance); // Fail Fast (see page 17)return (*iSingletonInstance);}This approach gives the caller more flexibility.