Singleton

Learning Objectives

Implement a thread-safe lazy singleton in C++ and compare other languages.

Explain lifetime, testing and global-state problems.

Replace singletons with dependency injection where appropriate.

Prerequisites: Modules 10, 22.

Intuition and Definition

Intent: ensure a class has exactly one instance and provide a global access point to it.

Forces: some resources are genuinely unique (a hardware register block, a process-wide configuration); creating several would be wrong or wasteful. But global access couples every caller to the concrete class and hides dependencies.

Participants: the Singleton class with a private constructor and a static instance() accessor.

Why It Matters

Singleton is the most used and most criticised pattern. Knowing its pitfalls — hidden dependencies, test pollution, initialization and destruction order, concurrency — is a standard interview topic.

Syntax and Language Notes

class Config {
public:
    static Config& instance() { static Config c; return c; }   // C++11: thread-safe lazy init
    Config(const Config&) = delete;
    Config& operator=(const Config&) = delete;
private:
    Config() = default;
};
// Java (not run): the enum singleton is serialization- and reflection-safe
public enum Registry { INSTANCE; public void register(String s) { } }
public sealed class Registry
{
    private static readonly Lazy<Registry> lazy = new(() => new Registry());   // thread-safe lazy
    public static Registry Instance => lazy.Value;
    private Registry() { }
}
# a module is already a singleton: import config; config.value = ...

Complete Example

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <vector>

class DiagnosticLog {
public:
    static DiagnosticLog& instance() {
        static DiagnosticLog log;          // constructed once, on first call, thread-safe
        return log;
    }
    DiagnosticLog(const DiagnosticLog&) = delete;
    DiagnosticLog& operator=(const DiagnosticLog&) = delete;
    void record(const std::string& code) { ++counts_[code]; }   // NOT thread-safe by itself!
    int count(const std::string& code) const { auto it = counts_.find(code); return it == counts_.end() ? 0 : it->second; }
private:
    DiagnosticLog() { ++constructions; }
    std::map<std::string, int> counts_;
public:
    static inline int constructions = 0;
};

// the DI alternative: depend on an interface, let main decide it's shared
struct Log { virtual ~Log() = default; virtual void record(const std::string&) = 0; };
struct MemoryLog : Log { std::vector<std::string> codes; void record(const std::string& c) override { codes.push_back(c); } };
class Sensor {
    Log& log_;
public:
    explicit Sensor(Log& l) : log_(l) {}
    void fault() { log_.record("P0117"); }
};

int main() {
    std::vector<std::thr
…
constructions after 8 threads: 1
U0100 count: 2
same object: 1
injected log entries: 2

Step-by-Step Walkthrough

Eight threads call instance() simultaneously; the C++11 "magic static" guarantees one construction.

Thread-safe *creation* does not make *use* thread-safe: record modifies a std::map and would need a mutex if called concurrently.

The Sensor alternative receives a Log&. The application still shares one log, but tests can pass a fresh MemoryLog and nothing depends on a global.

classDiagram
  class DiagnosticLog {
    -DiagnosticLog()
    +instance()$ DiagnosticLog
    +record(code)
  }
  Client ..> DiagnosticLog : instance()

Common Mistakes

Using Singleton for convenience (global access) rather than because one instance is *required*.

Hidden dependencies: functions that call X::instance() can't be tested without the real X.

Test pollution: state left in the singleton by one test leaks into the next.

Double-checked locking hand-rolled with non-atomic pointers (broken before C++11/Java 5 memory models).

Destruction-order problems: another static's destructor using the singleton after it was destroyed.

Trade-offs and Alternatives

Dependency injection of a shared instance Almost always for application code

Module-level object (Python, JS modules) Monostate (all-static data, normal objects) Rare; same global-state issues Container singleton lifetime (DI)

Real-World Use

Legitimate cases: a hardware abstraction for a unique peripheral, a process-wide logger *backend*, std::cout. Even then, prefer passing a reference to code that uses it.

Practice

Q1. (Easy) Why is the constructor private?

Answer: So no code outside the class can create additional instances.

Q2. (Medium) Is static T& instance() { static T t; return t; } safe if two threads call it at the same time?

Answer: Yes for initialization (C++11 guarantees it); member functions still need their own synchronization.

Q3. (Advanced) How would you make a singleton-dependent function testable without rewriting all callers at once?

Answer: Introduce an interface for the singleton's operations, give the function an overload or default parameter that accepts the interface (defaulting to the singleton), and migrate callers gradually — "parameterize from above". Tests pass a fake.

Interview Question

Q1. "Why is Singleton often called an anti-pattern?" Answer: Because its *global access point* is the problem, not the single instance: it hides dependencies, couples callers to a concrete class, makes unit tests share state and order-dependent, complicates concurrency (shared mutable state), and has lifetime issues at shutdown. The single-instance requirement is better met by creating one object at the composition root and injecting it.

Quick Quiz

Q1. What makes the C++ function-local static thread-safe?

Answer: The C++11 standard guarantees concurrent initialization of block-scope statics happens exactly once.

Q2. What is the Java enum singleton good for?

Answer: Serialization and reflection safety with no extra code.

Q3. Better alternative for most application code?

Answer: Dependency injection of one shared instance.

Summary

Singleton = one instance + global access; the global access is the costly part.

Use magic statics for safe lazy creation; still synchronize use.

Prefer DI; reserve singletons for truly unique resources.

Related: Module 10, Module 22, Module 60.