Singleton Pattern   «Prev  Next»
Lesson 12Singleton Class Course Project
ObjectiveWrite the Singleton Classes for the course project.

Write Singleton Classes for Course Project

In this module you were introduced to a very simple yet useful design pattern, the Singleton. For this part of the course project, you will be incorporating this pattern into the traffic flow system. You will be creating a Clock or Time class that insures that the time is the same across classes. This is not a real-time clock but a simulated clock.

Destroying the Singleton

As discussed, Singleton is created on demand, when Instance is first called. The first call to Instance defines the construction moment but leaves the destruction problem open: When should the singleton destroy its instance? The GoF book doesn't discuss this issue, but, as John Vlissides's book Pattern Hatching (1998) witnesses, the problem is thorny. Actually, if Singleton is not deleted, that's not a memory leak. Memory leaks appear when you allocate accumulating data and lose all references to it. This is not the case here: Nothing is accumulating, and we hold knowledge about the allocated memory until the end of the application. Furthermore, all modern operating systems take care of completely deallocating a process's memory upon termination.
  • Resource Leak:
    Singleton's constructor may acquire an unbound set of resources: network connections, handles to OS-wide mutexes and other interprocess communication means, references to COM objects. The only correct way to avoid resource leaks is to delete the Singleton object during the application's shutdown. The issue is that we have to choose the moment carefully so that no one tries to access the singleton after its destruction. The simplest solution for destroying the singleton is to rely on language mechanisms. For example, the following code shows a different approach to implementing a singleton. Instead of using dynamic allocation and a static pointer, the Instance function relies on a local static variable.
    Singleton& Singleton::Instance()
    {
    static Singleton obj;
    return obj;
    }
    

    This simple and elegant implementation was first published by Scott Meyers (Meyers 1996a, Item 26); therefore, we will refer to it as the "Meyers singleton". The Meyers singleton relies on some compiler magic. A function-static object is initialized when the control flow is first passing its definition. Don't confuse static variables that are initialized at runtime with primitive static variables initialized with compile-time constants. For example:
    int Fun()
    {
    static int x = 100;
    return ++x;
    }
    

    In this case, x is initialized before any code in the program is executed, most likely at load time. For all that Fun can tell when first called, x has been 100 since time immemorial. In contrast, when the initializer is not a compile-time constant, or the static variable is an object with a constructor, the variable is initialized at runtime during the first pass through its definition. In addition, the compiler generates code so that after initialization, the runtime support registers the variable for destruction. A pseudo-C++ representation of the generated code may look like the following code. (The variables starting with two underscores should be thought of as hidden, that is, variables generated and managed only by the compiler.)
    Singleton& Singleton::Instance()
    {
    // Functions generated by the compiler
    extern void __ConstructSingleton(void* memory);
    extern void __DestroySingleton();
    // Variables generated by the compiler
    static bool __initialized = false;
    // Buffer that holds the singleton
    // (We assume it is properly aligned)
    static char __buffer[sizeof(Singleton)];
    if (!__initialized)
    {
    // First call, construct object
    // Will invoke Singleton::Singleton
    // In the __buffer memory
    __ConstructSingleton(__buffer);
    // register destruction
    atexit(__DestroySingleton);
    __initialized = true;
    }
    return *reinterpret_cast<Singleton *>(__buffer);
    

    The core is the call to the atexit function. The atexit function, provided by the standard C library, allows you to register functions to be automatically called during a program's exit, in a last in, first out (LIFO) order. (By definition, destruction of objects in C++ is done in a LIFO manner: Objects created first are destroyed last. Of course, objects you manage yourself with new and delete don't obey this rule.) The signature of atexit is
    // Takes a pointer to function
    // Returns 0 if successful, or a nonzero value if an error occurs
    int atexit(void (*pFun)());
    

    The compiler generates the function __DestroySingleton, whose execution destroys the Singleton object seated in __buffer's memory and passes the address of that function to atexit. How does atexit work? Each call to atexit pushes its parameter on a private stack maintained by the C runtime library. During the application's exit sequence, the runtime support calls the functions registered with atexit.
    We will see in a short while that atexit has important, and sometimes unfortunate links with implementing the Singleton design pattern in C++. No matter what solution for destroying singletons we try, it has to play nice with atexit or else we break programmers' expectations.
    The Meyers Singleton provides the simplest means of destroying the singleton during an application's exit sequence. It works fine in most cases and we will study its problems and provide some improvements and alternate implementations for special cases.
  • The Meyer's Singleton, named after Scott Meyers, is a popular and efficient way to implement the Singleton design pattern in C++. It leverages the language's guarantee of thread-safe initialization of static local variables since C++11.
    Here's how it works:
    class Singleton {
    public:
      static Singleton& getInstance() {
        static Singleton instance; 
        return instance;
      }
    
    private:
      Singleton() {}  // Private constructor to prevent direct instantiation
      Singleton(const Singleton&) = delete;  // Prevent copy construction
      Singleton& operator=(const Singleton&) = delete;  // Prevent assignment
    };
    

    Explanation:
    • getInstance(): This static member function provides the only way to access the Singleton instance.
    • static Singleton instance;: This line within getInstance() declares a static local variable. Due to the static keyword, this instance is created only once upon the first call to getInstance(). Subsequent calls will simply return this existing instance.
    • Private constructor, copy constructor, and assignment operator: These prevent the Singleton from being copied or created directly, ensuring only one instance exists.

    Benefits of Meyer's Singleton:
    • Thread-safe: In C++11 and later, the initialization of static local variables is guaranteed to be thread-safe.
    • Lazy initialization: The Singleton instance is created only when getInstance() is called for the first time, saving resources if the Singleton is never used.
    • Simple and concise: The implementation is straightforward and easy to understand.

    Example Usage:
    int main() {
      Singleton& s1 = Singleton::getInstance();
      Singleton& s2 = Singleton::getInstance();
    
      // s1 and s2 will refer to the same instance
    }
    

    Important Notes:
    • While Meyer's Singleton is widely used and efficient, it's essential to be aware of potential drawbacks of the Singleton pattern in general, such as hindering testability and introducing global state.
    • In scenarios where strict control over the Singleton's destruction order is needed, more complex solutions might be required.


Singleton Course Project - Exercise

In this exercise, you will write the Singleton classes for the course project.
Singleton Course Project - Exercise

SEMrush Software 12 SEMrush Banner 12