C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
135: The Singleton Pattern in C++
I see this all the time when I'm reviewing code from developers moving into mid-level roles: they think a Singleton is just a class where every member is marked static. They essentially create a "namespace with a fancy name" and call it a pattern. But there is a massive difference between a collection of static functions and a Singleton.
Static Classes Aren't Singletons
If you just make your members static, you haven't actually controlled the lifecycle of an object; you've just created global state. You lose the ability to use inheritance, you can't implement interfaces, and you have zero control over when that state is initialized. Worse, if you try to implement a "naive" Singleton using a static pointer, you usually end up with a thread-safety nightmare.
// The "Naive" (and broken) way
class ConfigManager {
private:
static ConfigManager* instance;
ConfigManager() {} // Private constructor
public:
static ConfigManager* getInstance() {
if (instance == nullptr) {
// RACE CONDITION: Two threads could enter here simultaneously
instance = new ConfigManager();
}
return instance;
}
};
In the code above, if two threads call getInstance() at the exact same millisecond, you might end up with two different ConfigManager objects in memory. Now your "Single" ton is a doubleton, and your app is crashing in ways that are nearly impossible to debug.
The Meyers Singleton: The Gold Standard
You don't need complex mutexes or double-checked locking anymore. Since C++11, the language guarantees that static local variables are initialized in a thread-safe manner. This is known as the Meyers Singleton. It's cleaner, faster, and handles the cleanup for you.
Let's look at a real-world scenario: a LogService. You want one single point of truth for your logs across the entire application, but you don't want to pass a logger pointer into every single function in your codebase.
#include <iostream>
#include <string>
#include <mutex>
class LogService {
public:
// 1. Delete copy constructor and assignment operator
// We absolutely cannot allow copies of a Singleton.
LogService(const LogService&) = delete;
LogService& operator=(const LogService&) = delete;
static LogService& getInstance() {
// 2. Local static variable.
// C++11 guarantees this is initialized only once and is thread-safe.
static LogService instance;
return instance;
}
void log(const std::string& message) {
std::lock_guard<std::mutex> lock(logMutex);
std::cout < "[LOG]: " < message < std::endl;
}
private:
// 3. Private constructor prevents direct instantiation
LogService() {
std::cout < "LogService Initialized." < std::endl;
}
std::mutex logMutex;
};
Notice a few things here. First, I deleted the copy constructor and assignment operator. If you don't do this, someone could accidentally write LogService myLog = LogService::getInstance();, which creates a copy and completely defeats the purpose of the pattern. Second, we return a reference (LogService&) rather than a pointer. This tells the user that the instance is guaranteed to exist; they don't need to check for nullptr.
When to Actually Use This (And When to Run Away)
I'll be honest with you: the Singleton is often called an "anti-pattern." Why? Because it's essentially a global variable in a tuxedo. It makes unit testing a pain because you can't easily swap the Singleton for a "mock" object during tests.
Use a Singleton when you have a truly unique resource—like a hardware driver interface, a filesystem cache, or a global configuration coordinator—where having two instances would actually be a logic error or a resource conflict. If you're just using it because you're too lazy to pass a pointer through a few function calls, you're creating technical debt. Use it sparingly, and use it intentionally.
📋 Practical Task
Exercise: Implement a Thread-Safe GameAudioManager
You are building a game engine. The audio hardware can only be initialized once. If multiple instances of an audio manager try to seize the hardware, the game will crash.
Your Task: Create a class named GameAudioManager that implements the Singleton pattern using the Meyers approach. Your implementation must meet these requirements:
- The constructor must be private.
- The copy constructor and copy assignment operator must be deleted.
- It must provide a static method
getInstance()that returns a reference to the single instance. - It must have a method
playSound(std::string soundName)that prints"Playing sound: [soundName]"to the console. - Verify in your
main()function that callinggetInstance()multiple times returns the exact same memory address.
There are no comments for now.