|
1 | | -# Singleton pattern examples in C++ |
| 1 | +# Singleton Pattern Examples in C++ |
2 | 2 |
|
3 | | -A collection of minimal, self-contained C++ examples demonstrating multiple ways to implement the Singleton design pattern. The repository includes modern, thread-safe techniques (Meyer's Singleton) and legacy aproaches (Double-Checked Locking) for comparison. |
| 3 | +A collection of minimal, self-contained C++ examples demonstrating multiple ways to implement the Singleton design pattern. The repository includes modern, thread-safe techniques (Meyer's Singleton) and legacy approaches (Double-Checked Locking) for comparison. |
4 | 4 |
|
5 | 5 | ## 🔍 Overview |
6 | 6 |
|
| 7 | +<table> |
| 8 | + <thead> |
| 9 | + <tr> |
| 10 | + <th>Example</th> |
| 11 | + <th>Initialization</th> |
| 12 | + <th>Storage duration</th> |
| 13 | + <th>Cleanup</th> |
| 14 | + <th>Thread-safe Initialization</th> |
| 15 | + <th>Notes</th> |
| 16 | + </tr> |
| 17 | + </thead> |
| 18 | + <tbody> |
| 19 | + <tr> |
| 20 | + <td><code>singleton-classic-example</code></td> |
| 21 | + <td>Eager</td> |
| 22 | + <td>Static</td> |
| 23 | + <td>Automatic</td> |
| 24 | + <td>Yes</td> |
| 25 | + <td> |
| 26 | + Can suffer from SIOF and static destruction order issues. |
| 27 | + </td> |
| 28 | + </tr> |
| 29 | + <tr> |
| 30 | + <td><code>singleton-meyers-example</code></td> |
| 31 | + <td>Lazy</td> |
| 32 | + <td>Static</td> |
| 33 | + <td>Automatic</td> |
| 34 | + <td>Yes (since C++11)</td> |
| 35 | + <td> |
| 36 | + Avoids classic SIOF because the object is created on first use.<br><br> |
| 37 | + May still suffer from static destruction order issues during program shutdown. |
| 38 | + </td> |
| 39 | + </tr> |
| 40 | + <tr> |
| 41 | + <td><code>singleton-classic-dynamic-example</code></td> |
| 42 | + <td>Lazy</td> |
| 43 | + <td>Dynamic</td> |
| 44 | + <td>Manual</td> |
| 45 | + <td>No</td> |
| 46 | + <td> |
| 47 | + Races may occur if multiple threads call <code>getInstance()</code> or |
| 48 | + <code>delInstance()</code> concurrently.<br><br> |
| 49 | + Manual lifetime management is error-prone. |
| 50 | + </td> |
| 51 | + </tr> |
| 52 | + <tr> |
| 53 | + <td><code>singleton-cherno-example</code></td> |
| 54 | + <td>Lazy</td> |
| 55 | + <td>Dynamic</td> |
| 56 | + <td>Manual</td> |
| 57 | + <td>No</td> |
| 58 | + <td> |
| 59 | + Similar problems to <code>singleton-classic-dynamic-example</code>: manual cleanup, |
| 60 | + unsafe lifetime management and no thread safety. |
| 61 | + </td> |
| 62 | + </tr> |
| 63 | + <tr> |
| 64 | + <td><code>singleton-dclp-example</code></td> |
| 65 | + <td>Lazy</td> |
| 66 | + <td>Dynamic</td> |
| 67 | + <td>Manual</td> |
| 68 | + <td>No</td> |
| 69 | + <td> |
| 70 | + Classic Double-Checked Locking Pattern (DCLP).<br><br> |
| 71 | + Historically unsafe because of data races and reordering issues.<br><br> |
| 72 | + Obsolete in modern C++. |
| 73 | + </td> |
| 74 | + </tr> |
| 75 | + <tr> |
| 76 | + <td><code>singleton-leaky-example</code></td> |
| 77 | + <td>Lazy</td> |
| 78 | + <td>Dynamic</td> |
| 79 | + <td>None</td> |
| 80 | + <td>Yes (since C++11)</td> |
| 81 | + <td> |
| 82 | + Intentionally leaks memory to avoid shutdown-order problems.<br><br> |
| 83 | + Useful when destruction order is more dangerous than not deleting the instance. |
| 84 | + </td> |
| 85 | + </tr> |
| 86 | + </tbody> |
| 87 | +</table> |
| 88 | + |
| 89 | +--- |
| 90 | + |
7 | 91 | ### 1️⃣ singleton-classic-example |
8 | 92 |
|
9 | | -* 🔳 **Singleton with a static member instance.** |
10 | | -* 🧩 **Static member variable** |
11 | | -* 💾 **Static memory allocation** |
12 | | -* ⚡ **Eager initialization** → Created before `main` starts |
13 | | -* 🧼 **Automatic cleanup** → Destroyed after `main` exits |
14 | | -* 🔒 **Initialization is Thread-safe** |
15 | | - * The static member is initialized before `main` in a single-threaded context, so no construction race is possible. |
16 | | -* ⚠️ **Static Initialization Order Fiasco** |
17 | | - * Can suffer from SIOF If the singleton instance is accessed during the initialization of another static object, it may lead to UB due to the order of initialization. |
18 | | -* ⚠️ **Static Destruction Order Fiasco** |
19 | | - * A symmetric problem, If one static object's destructor calls another static that has been destroyed, it results in UB. |
| 93 | +🔳 **Singleton with a static member instance.** |
| 94 | +* 🧩 **Variable**: Static member variable |
| 95 | +* 💾 **Storage**: Static storage duration |
| 96 | +* ⚡ **Initialization:** Eager → Created before `main()` starts |
| 97 | +* 🧼 **Cleanup:** Automatic → Destroyed after `main()` exits |
| 98 | +* 🔒 **Thread safety:** Construction is effectively safe because initialization happens before `main()` in a single-threaded context |
| 99 | +* ⚠️ **Static Initialization Order Fiasco ([SIOF](https://en.cppreference.com/w/cpp/language/siof.html)):** |
| 100 | + * If a static object in another translation unit accesses the singleton during its own initialization, the singleton may not be constructed yet. |
| 101 | +* ⚠️ **Static Destruction Order Fiasco:** |
| 102 | + * If a static object in another translation unit accesses the singleton during its own destruction, the singleton may already have been destroyed. |
20 | 103 |
|
21 | 104 | ### 2️⃣ singleton-meyers-example |
22 | 105 |
|
23 | | -* 🔳 **Meyer's Singleton** → The simplest and safest modern C++ singleton implementation. |
24 | | -* 🧩 **Static local variable** |
25 | | -* 💾 **Static memory allocation** |
26 | | -* ⏳ **Lazy initialization** → Created only on first call to `getInstance()` |
27 | | -* 🧼 **Automatic cleanup** → Destroyed after `main` exits |
28 | | -* 🔒 **Initialization is Thread-safe since C++11** |
| 106 | +🔳 **Meyer's Singleton** → The simplest and safest modern C++ singleton implementation. |
| 107 | +* 🧩 **Variable**: Static local variable |
| 108 | +* 💾 **Storage**: Static storage duration |
| 109 | +* ⏳ **Initialization:** Lazy → Created only on first call to `getInstance()` |
| 110 | +* 🧼 **Cleanup:** Automatic → Destroyed after `main()` exits |
| 111 | +* 🔒 **Thread safety:** Initialization is thread-safe since C++11 |
29 | 112 | * A function-local static variable is initialized exactly once, even in a multi-threaded environment. |
30 | | -* ⚠️ The Meyer's Singleton fixes **Static Initialization Order Fiasco**, but can suffer from **Static Destruction Order Fiasco**. |
| 113 | +* ✅ **Fixes SIOF:** Avoids the classic static initialization order problem because the object is created on first use. |
| 114 | +* ⚠️ **Still vulnerable to Static Destruction Order Fiasco** |
31 | 115 |
|
32 | 116 | ### 3️⃣ singleton-classic-dynamic-example |
33 | | -* **Singleton with a static member pointer** → Dynamically allocated on first use. |
34 | | -* 🧩 **Static member pointer** |
35 | | -* 💾 **Dynamic memory allocation** |
36 | | -* ⏳ **Lazy initialization** → Created only on first call to `getInstance()` |
37 | | -* 🧹 **Manual cleanup** → Requires manual destruction via `delInstance()` |
38 | | -* ⚠️ **Not Thread-safe** |
| 117 | + |
| 118 | +🔳 **Singleton with a static member pointer** → Dynamically allocated on first use. |
| 119 | +* 🧩 **Variable**: Static member pointer |
| 120 | +* 💾 **Storage**: Dynamic storage duration |
| 121 | +* ⏳ **Initialization:** Lazy → Created only on first call to `getInstance()` |
| 122 | +* 🧹 **Cleanup:** Manual → Requires manual destruction via `delInstance()` |
| 123 | +* ⚠️ **Thread safety:** Not guaranteed |
39 | 124 | * Two threads could call `delInstance()` simultaneously, or one calls `getInstance()` while another calls `delInstance()`, leading to a race on the pointer. |
40 | | -* ⚠️ **Static Destruction Order Fiasco** |
41 | | - * If another static's destructor calls `getInstance()` after `delInstance()` has run, you're dereferencing a deleted pointer, right back to UB. |
42 | | -* ❗ Not recommended for multi-threaded applications. |
| 125 | +* ⚠️ **Manual lifetime management is error-prone** |
| 126 | + * Because the singleton is destroyed explicitly via `delInstance()`, the program must ensure no code still uses the old instance after deletion. |
43 | 127 |
|
44 | 128 |
|
45 | 129 | ### 4️⃣ singleton-cherno-example |
46 | | -* **Cherno-style Singleton** → [Why I don't like Singletons - Youtube](https://youtu.be/IMZMLvIwa-k?si=Q__9r--DOre6jahY) |
47 | | -* 🧩 **Static global variable** |
48 | | -* 💾 **Dynamic memory allocation** |
49 | | -* ⏳ **Lazy initialization** → Created only on first call to `getInstance()` |
50 | | -* 🧹 **Manual cleanup** → Requires manual destruction via `delInstance()` |
51 | | -* ⚠️ **Not Thread-safe** → Same as [singleton-classic-dynamic-example](#3️⃣-singleton-classic-dynamic-example) |
52 | | -* ❗ Not recommended for multi-threaded applications. |
| 130 | + |
| 131 | +🔳 **Cherno-style Singleton** — inspired by [Why I don't like Singletons - Cherno](https://youtu.be/IMZMLvIwa-k?si=Q__9r--DOre6jahY) |
| 132 | + |
| 133 | +* 🧩 **Variable**: Static global pointer |
| 134 | +* 💾 **Storage**: Dynamic storage duration |
| 135 | +* ⏳ **Initialization:** Lazy → Created only on first call to `getInstance()` |
| 136 | +* 🧹 **Cleanup:** Manual → Requires manual destruction via `delInstance()` |
| 137 | +* ⚠️ **Thread safety:** Not guaranteed, same as [singleton-classic-dynamic-example](#3️⃣-singleton-classic-dynamic-example) |
| 138 | +* ⚠️ **Manual lifetime management is error-prone** → Same as [singleton-classic-dynamic-example](#3️⃣-singleton-classic-dynamic-example) |
53 | 139 |
|
54 | 140 | ### 5️⃣ singleton-dclp-example |
55 | | -* **Singleton with Double-Checked Locking Pattern (DCLP)** → Classic but unsafe lazy-initialization pattern. |
56 | | -* 🧩 **Static member pointer** |
57 | | -* 💾 **Dynamic memory allocation** |
58 | | -* ⏳ **Lazy initialization** → Created only on first call to `getInstance()` |
59 | | -* 🧹 **Manual cleanup** → Requires manual destruction via `delInstance()` |
60 | | -* ⚠️ **Not Thread-safe** → Suffers from data races and reordering issues. |
| 141 | + |
| 142 | +🔳 **Singleton with Double-Checked Locking Pattern (DCLP)** → Classic, but unsafe lazy-initialization pattern. |
| 143 | +* 🧩 **Variable**: Static member pointer |
| 144 | +* 💾 **Storage**: Dynamic storage duration |
| 145 | +* ⏳ **Initialization:** Lazy → Created only on first call to `getInstance()` |
| 146 | +* 🧹 **Cleanup:** Manual → Requires manual destruction via `delInstance()` |
| 147 | +* ⚠️ **Thread safety:** Not guaranteed, suffers from data races and reordering issues. |
61 | 148 | * ❌ **DCLP is unreliable** → Multiple threads may observe a partially constructed object. |
62 | | -* ⛔ **Obsoleted by C++11** → Local static initialization is the correct modern solution. |
63 | | -* Reference: [C++ and the Perils of Double-Checked Locking by Scott Meyers and Andrei Alexandrescu](https://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf) |
| 149 | +* ⛔ **Largely obsolete since C++11** → Thread-safe function-local static initialization is the simpler and preferred modern solution. |
| 150 | +* 📖 **Reference:** [C++ and the Perils of Double-Checked Locking by Scott Meyers and Andrei Alexandrescu](https://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf) |
64 | 151 |
|
65 | 152 | ### 6️⃣ singleton-leaky-example |
66 | | -* 🔳 **"Leaky" Singleton** → A heap-allocated Meyer's Singleton. |
67 | | -* 🧩 **Static local pointer** |
68 | | -* 💾 **Dynamic memory allocation** |
69 | | -* ⏳ **Lazy initialization** → Created only on first call to `getInstance()` |
70 | | -* 🧹 **No cleanup** → You are intentionally leaking the memory. |
71 | | - * When the process ends, the Operating System reclaims the entire memory block anyway. "Leaking" at process exit is technically harmless. |
72 | | -* 🔒 **Initialization is Thread-safe since C++11** |
73 | | - * The local static pointer initialization is still protected by C++11's thread-safe static init guarantee, so the `new` only fires once, safely. |
74 | | -* ❎ **Avoids Static Initialization/Destruction Order Fiasco** |
75 | | - * Since the destructor is never called, it can't try to access other dead objects during shutdown. |
76 | 153 |
|
| 154 | +🔳 **"Leaky" Singleton** → A heap-allocated singleton initialized through a function-local static pointer. |
| 155 | +* 🧩 **Variable**: Static local pointer |
| 156 | +* 💾 **Storage**: Dynamic storage duration |
| 157 | +* ⏳ **Initialization:** Lazy → Created only on first call to `getInstance()` |
| 158 | +* 🧹 **Cleanup:** No cleanup, the object is intentionally never deleted. |
| 159 | + * The memory is intentionally left allocated until process termination. |
| 160 | + * In practice, the operating system reclaims the memory when the process exits. |
| 161 | +* 🔒 **Thread safety:** Initialization is thread-safe since C++11 |
| 162 | + * The local static pointer initialization is still protected by C++11's thread-safe static init guarantee, so the `new` only fires once, safely. |
| 163 | +* ✅ **Avoids both construction and destruction order problems** |
| 164 | + * Because the object is created on first use, it avoids SIOF |
| 165 | + * Because it is never destroyed, it avoids static destruction order issues |
| 166 | +* ⚠️ **Trade-off:** intentionally leaks memory by design |
77 | 167 | --- |
78 | 168 |
|
79 | 169 | ## ⚙️ Prerequisites |
@@ -176,4 +266,3 @@ build/windows-ninja-debug/singleton-meyers-example/02-singleton-meyers-example.e |
176 | 266 | ```bash |
177 | 267 | ./build/linux-ninja-debug/singleton-meyers-example/02-singleton-meyers-example |
178 | 268 | ``` |
179 | | - |
|
0 commit comments