Design Patterns «Prev Next»

Lesson 12Singleton Class Course Project
ObjectiveWrite the Singleton Classes for the course project.

Write Singleton Classes for the Course Project

Course project context: a shared clock in a simulated traffic system

In this module you learned the Singleton pattern: one shared instance with a well-known access point. In the course project, you will incorporate Singleton into a simulated traffic flow system by creating a Clock (or Time) class that ensures every part of the simulation agrees on the current time.

This is not a real-time clock. It is a simulation clock: time advances according to the rules of your program (for example, “tick once per loop iteration,” or “advance one second per event step”). The key requirement is consistency: one authoritative clock prevents different subsystems from drifting out of sync.

A useful design choice is to make the clock’s API explicit: one method to read time, and a controlled method to advance time. That keeps “timekeeping” centralized and prevents accidental edits from scattered code.


Destroying the Singleton (and why it matters)

Once you implement a singleton, the next question is lifecycle: when is the instance destroyed? A classic singleton is created “on demand” the first time Instance() (or getInstance()) is called. Creation is easy; destruction is subtle.

The important distinction is this:

  • Not deleting memory at exit is usually not a memory leak: when the process ends, modern operating systems reclaim the process’s memory.
  • But it can still be a resource leak: a singleton might hold resources that must be released cleanly: file handles, sockets, locks, OS objects, database connections, or references to external runtimes.

If your singleton owns external resources, the correct goal is a clean shutdown sequence where the singleton releases resources while ensuring no code tries to use it after teardown.

Resource leak risk and safe shutdown

A singleton’s constructor may acquire resources such as network connections, handles to OS-wide mutexes, interprocess communication primitives, or references to external components. If those resources must be closed explicitly, you should release them during application shutdown. The challenge is destruction order: destroy too early and you risk use-after-destruction; destroy too late and you risk holding resources longer than necessary.

The Meyers Singleton (function-local static)

A widely used and elegant approach in C++ is the Meyers singleton, which uses a function-local static object. Since C++11, initialization of function-local statics is guaranteed to be thread-safe.

Singleton& Singleton::Instance()
{
    static Singleton obj;
    return obj;
}

This approach avoids manual new/delete, and the runtime will destroy the singleton automatically during the program’s exit sequence. Under the hood, the runtime effectively registers a destructor call (often via an atexit-style mechanism), so destruction occurs when the program exits.

Compile-time vs runtime initialization (why the details matter)

It is useful to distinguish primitive statics initialized with compile-time constants from objects that require runtime construction. In the example below, the static int has a compile-time initializer:

int Fun()
{
    static int x = 100;
    return ++x;
}

When a static requires runtime construction (for example, an object with a constructor), initialization occurs the first time execution passes through its definition. The runtime also arranges for destruction at program exit, typically in a last-in, first-out order. This is convenient—but it can interact with other global/static objects in surprising ways if multiple translation units are involved.

The practical takeaway for this course project is straightforward: for most applications, the Meyers singleton is the simplest correct implementation. If you have strict requirements about destruction order (especially across multiple global objects), you may need a more explicit lifecycle strategy.

A modern “full” Meyers singleton class skeleton

For completeness, here is the canonical C++11+ version that also disables copying and assignment:

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
};

This enforces the intent: one instance, no copies, no accidental reassignment. If you must release resources deterministically, add an explicit shutdown() method and call it from your application’s controlled exit path.



Applying this to the traffic simulation clock

In the traffic flow system, your singleton clock should make two responsibilities unambiguous:

  1. Reading time: any subsystem can query the current simulation time.
  2. Advancing time: only the simulation driver (or a designated coordinator) advances the clock.

That separation is what makes the singleton useful: it prevents scattered code from “inventing time,” while keeping the model consistent. It also makes testing easier: you can reset the simulation clock at the start of a test, or drive it forward deterministically.


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