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:
- 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.
- 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.
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
main()starts.Singleton::getInstance()is called fora.- The function sees the local static object
instancehas not been created yet. - C++ constructs
instanceexactly once. - The constructor prints
Constructor called. - A reference to that same object is returned and stored in
a. Singleton::getInstance()is called again forb.- This time,
instancealready exists, so C++ does not create a new object. - The same object is returned again.
a.printAddress()andb.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.
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:
instanceis 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
| Approach | Memory management | Thread safety | Complexity | Recommended |
|---|---|---|---|---|
Raw pointer + new | Manual | Usually unsafe unless synchronized | Higher | No |
| Static local object | Automatic | Safe since C++11 | Low | Yes |
| Global object | Automatic | Depends on usage | Low | Sometimes |
std::unique_ptr singleton | Automatic but managed manually | Depends on implementation | Medium | Rarely needed |
Reference vs pointer return
| Return type | Meaning | Null possible | Typical choice |
|---|---|---|---|
Singleton* | Access by pointer | Yes | Older style |
Singleton& | Guaranteed valid instance | No | Preferred |
Singleton vs regular object ownership
| Concept | Best when | Trade-off |
|---|---|---|
| Singleton | Exactly one shared instance is needed | Global state |
| Regular object passed as dependency | Clear ownership and testability matter | More wiring in code |
| Static utility class | No object state is needed | Not suitable for stateful services |
Singleton vs global variable
| Feature | Singleton | Global variable |
|---|---|---|
| Access control | Better | Poor |
| Lazy initialization | Yes | Usually no |
| Can hide constructor | Yes | Not applicable |
| Testability | Still difficult | Difficult |
A singleton is often better than an uncontrolled global object, but it is still a form of global state.
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
staticover 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.
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
Loggerclass with a private constructor. - Add a
getInstance()method that returns the single logger instance. - Prevent copying of the
Loggerobject. - Add a
log()method that prints a message. - In
main(), call the logger multiple times and show that the same instance is reused.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.