I l@ve RuBoard Previous Section Next Section

6.5 The Dead Reference Problem

To make the discussion concrete, let's refer to an example that will be used throughout the rest of this chapter to validate various implementations. The example has the same traits as the Singleton pattern itself: It's easy to express and understand, but hard to implement.

Say we have an application that uses three singletons: Keyboard, Display, and Log. The first two model their physical counterparts. Log is intended for error reporting. Its incarnation can be a text file, a secondary console, or even a scrolling marquee on the LCD display of an embedded system.

We assume that Log's construction implies a certain amount of overhead, so it is best to instantiate it only if an error occurs. This way, in an error-free session, the program doesn't create Log at all.

The program reports to Log any error in instantiating Keyboard or Display. Log also collects errors in destroying Keyboard or Display.

Assuming we implement these three singletons as Meyers singletons, the program is incorrect. For example, assume that after Keyboard is constructed successfully, Display fails to initialize. Display's constructor creates Log, the error is logged, and it's likely the application is about to exit. At exit time, the language rule enters into action: The runtime support destroys local static objects in the reverse order of their creation. Therefore, Log is destroyed before Keyboard. If for some reason Keyboard fails to shut down and tries to report an error to Log, Log::Instance unwittingly returns a reference to the "shell" of a destroyed Log object. The program steps into the shady realm of undefined behavior. This is the dead-reference problem.

The order of construction and destruction of Keyboard, Log, and Display is not known beforehand. The need is to have Keyboard and Display follow the C++ rule (last created is first destroyed) but also to exempt Log from this rule. No matter when it was created, Log must be always destroyed after both Keyboard and Display so that it can collect errors from the destruction of either of these.

If an application uses multiple interacting singletons, we cannot provide an automated way to control their lifetime. A reasonable singleton should at least perform dead-reference detection. We can achieve this by tracking destruction with a static Boolean member variable destroyed_. Initially, destroyed_ is false. Singleton's destructor sets destroyed_ to true.

Before jumping to the implementation, it's time for an overhaul. In addition to creating and returning a reference to the Singleton object, Singleton::Instance now has an additional responsibility—detecting the dead reference. Let's apply the design guideline that says, One function, one responsibility. We therefore define three distinct member functions: Create, which effectively creates the Singleton object; OnDeadReference, which performs error handling; and the well-known Instance, which gives access to the unique Singleton object. Of these, only Instance is public.

Let's implement a Singleton that performs dead-reference detection. First, we add a static bool member variable called destroyed_ to the Singleton class. Its role is to detect the dead reference. Then we change the destructor of Singleton to set pInstance_ to zero and destroyed_ to true. Here's the new class and the OnDeadReference function:



// Singleton.h


class Singleton


{


public:


   Singleton& Instance()


   {


      if (!pInstance_)


      {


         // Check for dead reference


         if (destroyed_)


         {


            OnDeadReference();


         }


         else


         {


            // First call—initialize


            Create();


         }


      }


      return pInstance_;


   }


private:


   // Create a new Singleton and store a


   // pointer to it in pInstance_


   static void Create();


   {


      // Task: initialize pInstance_


      static Singleton theInstance;


      pInstance_ = &theInstance;


   }


   // Gets called if dead reference detected


   static void OnDeadReference()


   {


      throw std::runtime_error("Dead Reference Detected");


   }


   virtual ~Singleton()


   {


      pInstance_ = 0;


      destroyed_ = true;


   }


   // Data


   Singleton pInstance_;


   bool destroyed_;


   ... disabled 'tors/operator= ...


};





// Singleton.cpp


Singleton* Singleton::pInstance_ = 0;


bool Singleton::destroyed_ = false;


It works! As soon as the application exits, Singleton's destructor gets called. The destructor sets pInstance_ to zero and destroyed_ to true. If some longer-living object tries to access the singleton afterward, the control flow reaches OnDeadReference, which throws an exception of type runtime_error. This solution is inexpensive, simple, and effective.

    I l@ve RuBoard Previous Section Next Section