Skip to content

Commit e8f55fc

Browse files
author
y-yamasaki
committed
ブログ追加
1 parent 4d734d2 commit e8f55fc

3 files changed

Lines changed: 368 additions & 8 deletions

File tree

WebSite/content/blog/blog_00027.en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,4 @@ Design depends on experience and context. Patterns expand your "design vocabular
307307

308308
(These days it's rare not to be familiar with these patterns, but still.)
309309

310-
Next time I'll explain detailed usage cases for several patterns with code examples.
310+
Next time I'll explain detailed usage cases for several patterns with code examples.
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
---
2+
title: GoF Design Patterns - Singleton
3+
date: 2026-01-16
4+
category: Coding
5+
description: A practical guide to the Singleton pattern from the GoF collection, with C++ examples, thread-safety notes, usages, anti-patterns, and mermaid diagrams.
6+
tags: [Coding, DesignPattern]
7+
recommended: true
8+
thumbnail: assets/img/ogp.png
9+
---
10+
11+
Hello! I'm Pan-kun.
12+
13+
This article covers the Singleton pattern from the GoF (Gang of Four) design patterns series.
14+
It provides practical guidance including C++ example code, how to implement it, when to use it, important caveats, and visualizations using mermaid diagrams.
15+
16+
## Introduction
17+
18+
The Singleton pattern ensures that a class has only one instance within an application and provides a global access point to that single instance.
19+
20+
Key aspects that typically define a Singleton implementation:
21+
22+
- Private constructor: prevents direct instantiation from outside.
23+
- Static instance: the class holds the single instance.
24+
- Static accessor: a function to provide access to the single instance.
25+
26+
Wikipedia defines it as:
27+
28+
> "In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one 'single' instance."
29+
> (Source: https://en.wikipedia.org/wiki/Singleton_pattern)
30+
31+
From the GoF perspective, the pattern centralizes control of object creation and access, which is useful for shared resources such as configuration, loggers, or connection pools.
32+
33+
Article goals:
34+
35+
- Show representative C++ implementation patterns and their pros/cons
36+
- Explain thread-safe initialization, performance, and testability concerns
37+
- Outline alternatives (e.g., Dependency Injection) and anti-patterns
38+
39+
Primary references (selected):
40+
41+
- https://en.wikipedia.org/wiki/Singleton_pattern
42+
- https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
43+
- https://en.cppreference.com/w/cpp/thread/call_once
44+
45+
These references serve as the basis for the implementations and design guidance below.
46+
47+
## Typical Use Cases
48+
49+
- Centralized configuration management
50+
- Shared logger instance
51+
- Hardware resources or connection pools where a single manager instance suffices
52+
- Application-wide caches or factories
53+
54+
Because a Singleton creates a global instance, it can make testing harder. Before adopting it, always ask: "Is it really necessary?" and "Is this the single instance that should be globally accessible?"
55+
56+
## C++ Implementation Patterns
57+
58+
Several implementation strategies are common for the Singleton pattern:
59+
60+
- Eager initialization
61+
- Lazy initialization
62+
- Double-checked locking (DCL)
63+
- Initialization-on-demand holder (Meyers' Singleton style)
64+
65+
Below are a few C++ examples simplified for learning.
66+
67+
### Eager Singleton (simple)
68+
69+
Eager singleton is straightforward. In this approach the instance is created at program startup (static initialization). That means the creation cost happens during startup, and you may face static initialization order issues in complex programs.
70+
71+
```WebSite/content/other/blog_00028.en.md#L1-120
72+
// EagerSingleton.h
73+
#pragma once
74+
#include <iostream>
75+
76+
class EagerSingleton {
77+
private:
78+
static EagerSingleton instance; // created at program startup
79+
EagerSingleton() = default;
80+
public:
81+
EagerSingleton(const EagerSingleton&) = delete;
82+
EagerSingleton& operator=(const EagerSingleton&) = delete;
83+
84+
static EagerSingleton& GetInstance() {
85+
return instance;
86+
}
87+
88+
void Say() { std::cout << "EagerSingleton\n"; }
89+
};
90+
91+
// EagerSingleton.cpp
92+
EagerSingleton EagerSingleton::instance; // definition required
93+
```
94+
95+
### Lazy Singleton with mutex
96+
97+
A lazy singleton delays instance creation until first use. The example below uses a mutex to ensure thread safety. This approach is correct but incurs lock overhead on every access, which might be a performance concern.
98+
99+
```WebSite/content/other/blog_00028.en.md#L121-260
100+
// LazySingleton.h
101+
#pragma once
102+
#include <mutex>
103+
#include <memory>
104+
105+
class LazySingleton {
106+
private:
107+
static std::mutex mtx;
108+
static LazySingleton* instance;
109+
LazySingleton() = default;
110+
public:
111+
LazySingleton(const LazySingleton&) = delete;
112+
LazySingleton& operator=(const LazySingleton&) = delete;
113+
114+
static LazySingleton* GetInstance() {
115+
std::lock_guard<std::mutex> lock(mtx);
116+
if (!instance) instance = new LazySingleton();
117+
return instance;
118+
}
119+
120+
void Say();
121+
};
122+
123+
// LazySingleton.cpp
124+
std::mutex LazySingleton::mtx;
125+
LazySingleton* LazySingleton::instance = nullptr;
126+
```
127+
128+
This pattern may be appropriate if:
129+
- You can accept the extra lock cost, or
130+
- Your environment's performance characteristics make the lock cost negligible.
131+
132+
### Meyers' Singleton (recommended for C++11+)
133+
134+
Meyers' Singleton uses a function-local static variable. Since C++11, initialization of function-local static variables is thread-safe, so this provides a concise, safe lazy-initialization approach.
135+
136+
```WebSite/content/other/blog_00028.en.md#L261-360
137+
// MeyersSingleton.h
138+
#pragma once
139+
#include <iostream>
140+
141+
class MeyersSingleton {
142+
private:
143+
MeyersSingleton() { }
144+
public:
145+
MeyersSingleton(const MeyersSingleton&) = delete;
146+
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
147+
148+
static MeyersSingleton& GetInstance() {
149+
static MeyersSingleton instance; // thread-safe since C++11
150+
return instance;
151+
}
152+
153+
void Say() { std::cout << "MeyersSingleton\n"; }
154+
};
155+
```
156+
157+
Refer to cppreference for the guarantee that local static initialization is thread-safe: https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
158+
159+
### Lazy Singleton with std::call_once
160+
161+
`std::call_once` and `std::once_flag` provide an explicit and safe way to perform one-time initialization. This is useful for more complex initialization logic.
162+
163+
```WebSite/content/other/blog_00028.en.md#L361-520
164+
// CallOnceSingleton.h
165+
#pragma once
166+
#include <mutex>
167+
#include <memory>
168+
169+
class CallOnceSingleton {
170+
private:
171+
static std::unique_ptr<CallOnceSingleton> instance;
172+
static std::once_flag initFlag;
173+
CallOnceSingleton() = default;
174+
175+
static void Init() {
176+
instance.reset(new CallOnceSingleton());
177+
}
178+
public:
179+
CallOnceSingleton(const CallOnceSingleton&) = delete;
180+
CallOnceSingleton& operator=(const CallOnceSingleton&) = delete;
181+
182+
static CallOnceSingleton& GetInstance() {
183+
std::call_once(initFlag, &CallOnceSingleton::Init);
184+
return *instance;
185+
}
186+
187+
void Say();
188+
};
189+
190+
// CallOnceSingleton.cpp
191+
std::unique_ptr<CallOnceSingleton> CallOnceSingleton::instance;
192+
std::once_flag CallOnceSingleton::initFlag;
193+
```
194+
195+
Useful reference: https://en.cppreference.com/w/cpp/thread/call_once
196+
197+
### Practical extensions and variations
198+
199+
Once you understand the basic pattern you can adapt it to various needs. The following are examples and not necessarily the best solution for every situation.
200+
201+
Template-based eager singleton: avoids repeating singleton boilerplate but may reduce readability as it hides the singleton nature in a template.
202+
203+
```WebSite/content/other/blog_00028.en.md#L521-700
204+
// TemplateEagerSingleton.h
205+
#pragma once
206+
#include <iostream>
207+
208+
template<typename T>
209+
class TemplateEagerSingleton {
210+
private:
211+
static T instance; // created at program startup
212+
TemplateEagerSingleton() = delete;
213+
public:
214+
TemplateEagerSingleton(const TemplateEagerSingleton&) = delete;
215+
TemplateEagerSingleton& operator=(const TemplateEagerSingleton&) = delete;
216+
217+
static T& GetInstance() {
218+
return instance;
219+
}
220+
};
221+
222+
// static member definition (for templates, definition can be in header)
223+
template<typename T>
224+
T TemplateEagerSingleton<T>::instance;
225+
226+
// Foo.h
227+
struct Foo {
228+
void Say() { std::cout << "TemplateEagerSingleton MyService\n"; }
229+
};
230+
231+
// other.cpp
232+
auto& foo = TemplateEagerSingleton<Foo>::GetInstance();
233+
foo.Say();
234+
```
235+
236+
Multi-instance eager approach: allows a fixed number of globally accessible instances. This changes the semantics from strict singleton to a small pool of global instances — lifecycle management and use-case justification should be considered.
237+
238+
```WebSite/content/other/blog_00028.en.md#L701-920
239+
// MultiInstanceEagerSingleton.h
240+
#pragma once
241+
#include <array>
242+
#include <iostream>
243+
#include <cstddef>
244+
#include <cassert>
245+
246+
class MultiInstanceEagerSingleton {
247+
private:
248+
static constexpr std::size_t COUNT = 3;
249+
static std::array<MultiInstanceEagerSingleton, COUNT> instances; // created at startup
250+
int id_;
251+
MultiInstanceEagerSingleton(int id = 0) : id_(id) {}
252+
public:
253+
MultiInstanceEagerSingleton(const MultiInstanceEagerSingleton&) = delete;
254+
MultiInstanceEagerSingleton& operator=(const MultiInstanceEagerSingleton&) = delete;
255+
256+
// Access one of several instances by index
257+
static MultiInstanceEagerSingleton& GetInstance(std::size_t index) {
258+
assert(index < COUNT && "index out of range");
259+
return instances[index];
260+
}
261+
262+
void Say() { std::cout << "MultiInstanceEagerSingleton id=" << id_ << '\n'; }
263+
};
264+
265+
// MultiInstanceEagerSingleton.cpp
266+
#include "MultiInstanceEagerSingleton.h"
267+
268+
// static member definition (COUNT instances created at startup)
269+
std::array<MultiInstanceEagerSingleton, MultiInstanceEagerSingleton::COUNT> MultiInstanceEagerSingleton::instances = {
270+
MultiInstanceEagerSingleton(0),
271+
MultiInstanceEagerSingleton(1),
272+
MultiInstanceEagerSingleton(2)
273+
};
274+
275+
// other.cpp
276+
auto& s0 = MultiInstanceEagerSingleton::GetInstance(0);
277+
s0.Say();
278+
```
279+
280+
## When to use and alternatives
281+
282+
Before adopting a Singleton, consider:
283+
284+
- Does the application truly need exactly one instance?
285+
- Do tests need to substitute the instance?
286+
- Is thread-safe initialization guaranteed for your chosen approach?
287+
288+
Alternatives:
289+
290+
- Dependency Injection (DI): Pass dependencies into classes so tests can inject mocks/stubs.
291+
- Factory / Service Locator: Provides centralized management; Service Locator can hide global dependencies so use with caution.
292+
- Module-scoped singletons: In C++, managing a shared object at module/translation-unit scope may suffice.
293+
294+
## Testability
295+
296+
Singletons introduce global state, which can make unit testing harder. Consider:
297+
298+
- Providing test-only initialization/teardown hooks (careful to avoid leaking test-only APIs into production).
299+
- Allowing construction to be supplied externally so tests can inject mocks.
300+
- Meyers' Singleton (function-local static) lives until process exit and is difficult to reset; consider process-level isolation for tests or using DI for testability.
301+
302+
## Anti-patterns and pitfalls
303+
304+
- Overuse: Wrapping global variables in a Singleton just for convenience increases coupling.
305+
- Hidden global state: Dependencies become implicit, making testing and refactoring hard.
306+
- Lifecycle ambiguity: Static initialization order issues and unclear shutdown semantics when multiple static objects interact.
307+
308+
## Summary
309+
310+
- Singleton enforces a single instance and provides a global access point, but it comes with design trade-offs: testability, coupling, and lifecycle concerns.
311+
- In C++11 and later, the function-local static (Meyers' Singleton) provides a concise, thread-safe option.
312+
- Use `std::call_once` for explicit and robust one-time initialization when you need more control.
313+
- For large systems or when testability is a priority, prefer DI or factory-based approaches over singletons.
314+
315+
References
316+
317+
- "Singleton pattern" — Wikipedia: https://en.wikipedia.org/wiki/Singleton_pattern
318+
- Quote: "In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one 'single' instance."
319+
- C++: Static local variables (thread-safety): https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
320+
- Quote: "Since C++11, the initialization of local static variables is thread-safe."
321+
- C++: std::call_once: https://en.cppreference.com/w/cpp/thread/call_once
322+
- Quote: "Ensures that the function f is called exactly once, even if called from several threads."
323+
324+
## Mermaid diagrams
325+
326+
Class diagram (Singleton and consumer relationship):
327+
328+
```WebSite/content/other/blog_00028.en.md#L921-980
329+
%%{init:{'theme':'default'}}%%
330+
classDiagram
331+
class MeyersSingleton {
332+
+static MeyersSingleton& GetInstance()
333+
+void Say()
334+
}
335+
class Consumer {
336+
+void useSingleton()
337+
}
338+
Consumer --> MeyersSingleton : uses
339+
```
340+
341+
Sequence diagram (first call vs subsequent calls):
342+
343+
```WebSite/content/other/blog_00028.en.md#L981-1040
344+
sequenceDiagram
345+
participant Client
346+
participant Singleton
347+
Client->>Singleton: GetInstance() // 1st call
348+
alt not initialized
349+
Singleton-->>Singleton: initialize (constructor)
350+
Singleton-->>Client: return instance
351+
else already initialized
352+
Singleton-->>Client: return instance
353+
end
354+
```
355+
356+
That’s it for this article. Next time we'll cover another GoF pattern with C++ examples and mermaid diagrams. The implementations shown here are simplified for learning; evaluate requirements (threads, lifecycle, testability) carefully before adopting in production.
357+
358+
— Pan-kun

0 commit comments

Comments
 (0)