Question

How to Implement the Singleton Design Pattern in C++ Correctly

cppdesign-patternssingleton

Question

In C++, I came across this implementation of the Singleton design pattern:

class Singleton
{
public:
    static Singleton* getInstance();
    ~Singleton();

private:
    Singleton();
    static Singleton* instance;
};

From this declaration, it looks like instance may be created on the heap, which implies dynamic memory allocation. I am unsure when that memory would be released.

  • Is the allocated memory ever deallocated automatically?
  • Does this implementation risk causing a memory leak?
  • If this approach is problematic, what is the correct way to implement a Singleton in C++?

I would like to understand both the memory-management issue and the recommended modern C++ approach.

Short Answer

By the end of this page, you will understand what the Singleton pattern is, why older heap-based implementations are often unsafe or unnecessary, and how to implement a Singleton correctly in modern C++. You will also learn about object lifetime, destruction, thread safety, and when Singleton should be avoided altogether.

Concept

The Singleton design pattern ensures that a class has only one instance and provides a global access point to that instance.

In older C++ code, Singleton was often implemented using a static pointer and new, like this:

Singleton* Singleton::instance = nullptr;

Singleton* Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    }
    return instance;
}

This raises an important question: who deletes the object?

If no code ever calls delete instance;, then the program leaks memory. In some older programs, developers accepted this because the operating system reclaims process memory when the program exits. But that is still poor design:

  • the destructor may never run
  • cleanup logic may be skipped
  • ownership is unclear
  • thread safety is not guaranteed

Why this matters

In real programs, object lifetime matters. A singleton may manage:

  • configuration
  • logging
  • database access
  • caches
  • shared services

If its lifetime is not handled correctly, you can get:

  • memory leaks
  • shutdown-order bugs
  • race conditions
  • hard-to-test code

The modern C++ approach

The usual modern implementation uses a function-local static object:

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

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

private:
    Singleton() = default;
    ~Singleton() = default;
};

This is often called a Meyers' Singleton.

Why it is better:

  • no manual new
  • no manual delete
  • object is destroyed automatically at program shutdown
  • initialization is thread-safe in modern C++ (since C++11)
  • simpler and clearer ownership

So the main lesson is: prefer automatic lifetime management over manual heap allocation unless you have a very specific reason not to.

Mental Model

Think of a Singleton like a single shared office printer in a company.

  • There should only be one printer.
  • Everyone who needs it uses the same one.
  • You do not want each employee creating a new printer.

Now imagine two ways to manage it:

  1. Heap-based version: someone buys a printer and puts the receipt in a drawer, but nobody knows who is responsible for throwing it away later.
  2. Local static version: the building automatically installs one printer the first time anyone needs it, and the building management removes it when the office closes.

The second model is safer because responsibility is clear.

That is what a function-local static does in C++: it creates the object once, stores it safely, and destroys it automatically at the end of the program.

Take Quiz

Syntax and Examples

Preferred syntax in modern C++

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

    void sayHello() {
        std::cout << "Hello from the singleton\n";
    }

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

private:
    Singleton() = default;
    ~Singleton() = default;
};

Usage:

#include <iostream>

int main() {
    Singleton& s = Singleton::getInstance();
    s.sayHello();
}

Why return a reference instead of a pointer?

Returning a reference:

static Singleton& getInstance();

is often better than returning a pointer because:

  • the instance always exists once returned
  • callers do not need to check for nullptr
  • it communicates that ownership is not transferred

Example with state

#include <iostream>
#include <string>

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

    void log(const std::string& message) {
        std::cout << "[LOG] " << message << '\n';
    }

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

private:
    Logger() = default;
    ~Logger() = default;
};

int main() {
    Logger::getInstance().log("Application started");
    Logger::getInstance().log("Loading configuration");
}

This creates one Logger object and reuses it everywhere.

Older heap-based style

class Singleton {
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

private:
    Singleton() = default;
    static Singleton* instance;
};

Singleton* Singleton::instance = nullptr;

This works in a basic sense, but it has problems:

  • memory must be freed manually
  • destructor may not run
  • not thread-safe without extra synchronization
  • more complex than necessary

That is why the local static version is preferred.

Step by Step Execution

Consider this code:

#include <iostream>

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

    void printAddress() {
        std::cout << this << '\n';
    }

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

private:
    Singleton() {
        std::cout << "Constructor called\n";
    }
};

int main() {
    Singleton& a = Singleton::getInstance();
    Singleton& b = Singleton::getInstance();

    a.printAddress();
    b.printAddress();
}

What happens step by step

  1. main() starts.
  2. Singleton::getInstance() is called for a.
  3. The function sees the local static object instance has not been created yet.
  4. C++ constructs instance exactly once.
  5. The constructor prints Constructor called.
  6. A reference to that same object is returned and stored in a.
  7. Singleton::getInstance() is called again for b.
  8. This time, instance already exists, so C++ does not create a new object.
  9. The same object is returned again.
  10. a.printAddress() and b.printAddress() print the same memory address.

Expected output pattern

Constructor called
0x12345678
0x12345678

The exact address will differ, but both lines should match.

This proves that only one object was created.

Real World Use Cases

Singletons are used when an application truly needs one shared instance.

Common examples

  • Logger: one central logging service used across the app
  • Configuration manager: one shared source of app settings
  • Application state: one global object representing runtime state
  • Resource registry: one place to access registered services
  • Hardware or OS wrapper: one interface to a unique system resource

Example scenarios

Logging

A desktop app may use one logger so all modules write messages consistently.

Configuration

A server may load settings once at startup and let the rest of the code read them from one shared object.

Metrics collection

A backend service may keep one metrics collector that records counters and timings.

When not to use Singleton

Singleton is often overused. Avoid it when:

  • you can pass an object as a parameter instead
  • you want easy unit testing
  • different parts of the program may need different instances
  • hidden global state would make the code harder to understand

In many codebases, dependency injection or regular object ownership is a better choice.

Take Quiz

Real Codebase Usage

In real projects, developers often use the idea behind Singleton more carefully than textbook examples suggest.

Common patterns

1. Local static instance

This is the most common modern C++ pattern:

static Service& getInstance() {
    static Service instance;
    return instance;
}

Useful when one instance is truly required and global access is acceptable.

2. Deleted copy operations

To preserve the singleton guarantee, copying is disabled:

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

Sometimes move operations are deleted too.

3. Guarded initialization logic

A singleton may validate setup before use:

void setConfigPath(const std::string& path) {
    if (path.empty()) {
        throw std::invalid_argument("path cannot be empty");
    }
    configPath = path;
}

4. Early return accessors

Code using the singleton often relies on easy access:

Logger::getInstance().log("Something happened");

5. Avoiding raw ownership

Even when developers need shared global access, they often avoid raw pointers. They prefer:

  • local statics
  • smart pointers with clear ownership
  • dependency injection containers

Important practical concern: destruction order

If multiple global or static objects depend on each other during shutdown, a singleton can cause static destruction order problems.

For example, if one global object tries to use the singleton while the singleton is already being destroyed, the program may behave incorrectly.

That is one reason many teams keep singleton usage minimal.

Common Mistakes

1. Allocating with new and never deleting

Broken example:

class Singleton {
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

private:
    static Singleton* instance;
};

Problem:

  • instance is never deleted
  • destructor may never run

Better:

static Singleton& getInstance() {
    static Singleton instance;
    return instance;
}

2. Forgetting to disable copying

Broken example:

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

private:
    Singleton() = default;
};

int main() {
    Singleton a = Singleton::getInstance(); // creates a copy
}

Fix:

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

3. Returning a raw pointer without need

Broken style:

static Singleton* getInstance();

This suggests nullable access or ownership confusion.

Prefer:

static Singleton& getInstance();

4. Ignoring thread safety in old code

Broken example:

if (instance == nullptr) {
    instance = new Singleton();
}

In multithreaded code, two threads might create two objects at the same time unless synchronized.

In modern C++, function-local static initialization is thread-safe.

5. Making everything global just because you can

A singleton introduces global state. This can make code:

  • harder to test
  • tightly coupled
  • harder to reason about

Use it only when one shared instance is truly required.

6. Public constructor by mistake

Broken example:

class Singleton {
public:
    Singleton() = default;
};

If the constructor is public, anyone can create more objects.

Fix: make the constructor private or protected.

Comparisons

Singleton implementation approaches

ApproachMemory managementThread safetyComplexityRecommended
Raw pointer + newManualUsually unsafe unless synchronizedHigherNo
Static local objectAutomaticSafe since C++11LowYes
Global objectAutomaticDepends on usageLowSometimes
std::unique_ptr singletonAutomatic but managed manuallyDepends on implementationMediumRarely needed

Reference vs pointer return

Return typeMeaningNull possibleTypical choice
Singleton*Access by pointerYesOlder style
Singleton&Guaranteed valid instanceNoPreferred

Singleton vs regular object ownership

ConceptBest whenTrade-off
SingletonExactly one shared instance is neededGlobal state
Regular object passed as dependencyClear ownership and testability matterMore wiring in code
Static utility classNo object state is neededNot suitable for stateful services

Singleton vs global variable

FeatureSingletonGlobal variable
Access controlBetterPoor
Lazy initializationYesUsually no
Can hide constructorYesNot applicable
TestabilityStill difficultDifficult

A singleton is often better than an uncontrolled global object, but it is still a form of global state.

Take Quiz

Cheat Sheet

Modern C++ Singleton template

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

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

private:
    Singleton() = default;
    ~Singleton() = default;
};

Rules

  • Make the constructor private.
  • Provide one access point, usually getInstance().
  • Return a reference when possible.
  • Delete copy constructor and copy assignment.
  • Prefer function-local static over raw pointer + new.
  • Since C++11, local static initialization is thread-safe.

Avoid

instance = new Singleton();

unless you have a very specific lifetime-management reason.

Good signs

  • no raw ownership
  • no manual deletion
  • simple implementation
  • clear single-instance guarantee

Edge cases

  • shutdown order between static objects can still be tricky
  • singleton use can make testing harder
  • many singletons are better replaced by dependency injection

FAQ

Does a singleton created with new leak memory in C++?

Yes, if nothing calls delete on it. The operating system may reclaim memory at process exit, but the object's destructor will not run automatically.

What is the best way to implement a Singleton in modern C++?

Use a function-local static object and return it by reference.

Is a local static singleton thread-safe?

Yes, initialization of function-local statics is thread-safe in C++11 and later.

Why should I delete the copy constructor in a singleton?

Because copying would allow more than one instance, which breaks the singleton rule.

Should getInstance() return a pointer or a reference?

A reference is usually better because it expresses that the object exists and is not nullable.

Is Singleton considered bad practice?

Not always, but it is often overused. It introduces global state, which can hurt testability and design clarity.

Can I use std::unique_ptr for a singleton?

You can, but it is usually unnecessary if a function-local static object already solves the lifetime problem cleanly.

Take Quiz

Mini Project

Description

Build a simple application logger as a singleton. This project demonstrates how one shared object can be accessed from different parts of a program without creating multiple logger instances. It also reinforces safe singleton construction using a local static object.

Goal

Create a Logger singleton that prints messages and proves that every call uses the same instance.

Requirements

  • Create a Logger class with a private constructor.
  • Add a getInstance() method that returns the single logger instance.
  • Prevent copying of the Logger object.
  • Add a log() method that prints a message.
  • In main(), call the logger multiple times and show that the same instance is reused.
Take Quiz

Keep learning

Related questions

View more