#cpp
#singleton
π In-Depth C++ Singleton Pattern: Principles, Implementation Comparisons, and shared_ptr Architecture Design
1οΈβ£ What is a Singleton? Basic Semantics and Usage Scenarios
A Singleton ([[Singleton]]) is one of the most common object creation patterns, aimed at ensuring that a class has only one instance in the system and provides a global access point.
β Common Usage Scenarios:
- Configuration Center (ConfigManager)
- Logging System (Logger)
- Resource Pool (e.g., memory pool, thread pool)
- Scheduler, Session Manager
- Framework Registry, Plugin System
2οΈβ£ Five Common Implementations of Singleton
Recommended Usage:
- Lazy Initialization + Thread-Safe (C++11 Standard) Local Static Variable (Meyers Singleton) β γRecommendedγ
- shared_ptr Singleton (more flexible)
π1. Eager Initialization Singleton (Early Instantiation)
class Singleton {
public:
static Singleton& instance() {
return inst;
}
private:
Singleton() {}
static Singleton inst;
};
Singleton Singleton::inst;
- β Simple and easy to understand, thread-safe (ensured by C++ static object initialization)
- β Resource may be wasted, instance constructed at program startup
π2. Lazy Initialization + Non-Thread-Safe
class Singleton {
public:
static Singleton* getInstance() {
if (!instance_)
instance_ = new Singleton;
return instance_;
}
private:
Singleton() {}
static Singleton* instance_;
};
Singleton* Singleton::instance_ = nullptr;
- β Instance created only when needed
- β Not thread-safe, multiple threads may construct multiple instances simultaneously
π3. Lazy Initialization + Thread-Safe (C++11 Standard) Local Static Variable (Meyers Singleton) β γRecommendedγ
class Singleton {
public:
static Singleton& instance() {
static Singleton inst; // C++11 guarantees thread safety
return inst;
}
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
- β Recommended: thread-safe, lazy-loaded, no lock overhead
- β Automatic management of destruction, relies on C++ static local variable characteristics
- β Copy construction and assignment are prohibited to prevent instance copying
- β The simplest, safest, and most efficient method (default recommendation)
Later, there will be a syntax semantics and underlying source code analysis of this method
π4. Lazy Initialization + Double-Checked Locking (DCLP)
class Singleton {
public:
static Singleton* instance() {
if (!inst) {
std::lock_guard<std::mutex> lock(mtx);
if (!inst) inst = new Singleton;
}
return inst;
}
private:
static Singleton* inst;
static std::mutex mtx;
};
- β Lazy loading, suitable for scenarios requiring dynamic construction and destruction control
- β Complex implementation, prone to errors
- β οΈ Easy to make mistakes in implementations before C++11 (lack of memory barriers)
π5. Smart Pointer + Lazy Construction
class Singleton {
public:
static std::shared_ptr<Singleton> instance() {
static std::shared_ptr<Singleton> inst(new Singleton);
return inst;
}
};
Supports custom destruction, more suitable for resource lifecycle management
π6. Template-based shared_ptr Singleton (Advanced) β The Right Way to Use Modern C++
template<typename T>
class SharedSingleton {
public:
using Ptr = std::shared_ptr<T>;
static void setDeleter(std::function<void(T*)> deleter) {
getDeleter() = std::move(deleter);
}
template<typename... Args>
static Ptr instance(Args&&... args) {
std::call_once(getOnceFlag(), [&] {
instance_() = Ptr(new T(std::forward<Args>(args)...), getDeleter());
});
return instance_();
}
static void reset() {
std::lock_guard<std::mutex> lock(getMutex());
instance_().reset();
getOnceFlag() = std::once_flag();
}
private:
static Ptr&& instance_() {
static Ptr inst;
return inst;
}
static std::once_flag&& getOnceFlag() {
static std::once_flag flag;
return flag;
}
static std::mutex&& getMutex() {
static std::mutex mtx;
return mtx;
}
static std::function<void(T*)>&& getDeleter() {
static std::function<void(T*)> deleter = [](T* p) { delete p; };
return deleter;
}
};
3οΈβ£ Implementation Comparison Analysis
| Implementation | Construction Control | Destruction Control | Supports Parameters | Thread-Safe | Lifecycle Control | Engineering Recommendation |
|---|---|---|---|---|---|---|
| Eager Initialization | β | β | β | β | β | π‘ |
| Lazy Initialization + DCLP | β | β | β | β οΈ | β | β (High Risk) |
| Meyers Singleton | β | β | β | β (C++11) | β | β |
| shared_ptr Static | β | β | β | β | Partial | β |
| shared_ptr Template Encapsulation | β | β | β | β | β | β β β |
4οΈβ£ Lazy Initialization + Thread-Safe (C++11 Standard) Local Static Variable (Meyers Singleton) β Syntax Semantics and Underlying Source Code Analysis
π Thread Safety of Local Static Variables (C++11)
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
C++11 Standard <span>[stmt.dcl/4]</span><span>: [[Initialization of Local Static Variables]] is [[Thread-Safe]].</span>
The [[compiler]] locks before initialization, ensuring that only one thread can construct it.
The compiler generates equivalent pseudo-code (illustrative):
Singleton& getInstance() {
static bool initialized = false;
static char storage[sizeof(Singleton)];
static std::once_flag flag;
std::call_once(flag, [&] {
new (&storage) Singleton();
initialized = true;
});
return *reinterpret_cast<Singleton*>(&storage);
}
π Destruction Order and Static Object Lifecycle Issues
- Local
<span>static</span>instances will be automatically [[destructed]] after exiting<span>main()</span> - If you want to manually control the lifecycle (e.g., still exist after
<span>exit()</span>), you need to use<span>new</span>with<span>atexit</span>or<span>shared_ptr</span><span> implementation.</span>
π Source Code Level Analysis (glibc + clang libc++)
At the [[glibc]] level:<span>pthread_once</span><span> implementation</span>
In [[glibc]], <span>std::call_once</span><span> relies on </span><code><span>pthread_once</span><span>:</span>
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
<span>pthread_once</span> uses locks + state flags + [[memory barrier]] to ensure initialization occurs only once, which is the basis for [[C++11]] <span>std::call_once</span>.
[[libc++]] implementation (<span><mutex></span><span>)</span>
namespace std {
template<class Callable>
void call_once(once_flag&& flag, Callable&& f);
}
Internally, it uses a double state atomic marker (<span>__called</span><span>, </span><code><span>__complete</span><span>)</span>
In the GCC implementation, it uses <span>__atomic_load_n</span><span> + </span><code><span>__sync_bool_compare_and_swap</span><span> to ensure [[atomicity]].</span>
5οΈβ£ Underlying Principle Analysis: Semantics + ABI Behavior + Construction Order
π§΅ <span>std::call_once</span><span> + </span><code><span>std::once_flag</span>
static std::once_flag f;
std::call_once(f, [] { singleton = new Singleton(); });
π₯ Memory Model Analysis
- Local static: stored in static area
- shared_ptr: heap resources, released by reference counting
6οΈβ£ ResourcePool / ThreadPool Engineering Practice
auto pool = SharedSingleton<BufferPool>::instance(32, 8*1024);
auto buf = pool->acquire();
auto pool = SharedSingleton<ThreadPool>::instance(8);
pool->enqueue([] { do_work(); });
β Summary and Design Philosophy
| Capability | Meyers Singleton | shared_ptr Encapsulation |
|---|---|---|
| Safety | β | β |
| Flexibility | β | β β |
| Lifecycle Control | β | β β β |
| Test Friendliness | β | β β |
| Engineering Adaptability | π‘ | β β β |