有名な話であると思っていたが、つい最近嵌った同僚がいたのでメモすることにした。
注)下記内容は Visual C++ / C++11 未対応の g++ を使用している場合の話です
Singleton パターン
デザインパターンの一つ。あるクラスが一つのインスタンスしか持たないことを確実にしたい時に使用する。C++ での Singleton パターンの実装例は下記の通り。
// Singleton.h
#pragma once
class Singleton
{
public:
static Singleton &getInstance();
private:
Singleton(){}
Singleton(const Singleton &other){}
Singleton &operator=(const Singleton &other){}
};
// Singleton.cpp
#include "Singleton.h"
Singleton &Singleton::getInstance() {
static Singleton instance;
return instance;
}
// main.cpp
#include <iostream>
#include "Singleton.h"
int main()
{
Singleton &singleton = Singleton::getInstance();
// do something...
return 0;
}
上記実装の問題点
Visual C++ / C++11 未対応の g++ を使用している場合、 上で示した実装にはスレッドセーフでないという問題がある。
例えば二つのスレッドが同時に Singleton::getInstance() を呼び出した場合、
- Singleton インスタンスが二回作成される
- 一方のスレッドで完全に初期化される前にもう片方のスレッドで Singleton インスタンスが使用される
のいずれかの問題が発生する可能性がある。
解決策 1 : 静的な初期化を行う
プログラムがまだシングルスレッドであると想定できる main() の前で Singleton クラスのインスタンスを作成すれば良い。静的な初期化を行うためには、下記コードを Singleton.cpp に追加すれば良い。
// Singleton.cpp
// 上で示した部分は省略
static Singleton &singleton = Singleton::getInstance();
解決策 2 : Singleton クラスの初期化ルーチンを追加する
例えば下記 init() の様な、Singleton クラス用に初期化ルーチンを追加する。
なお下記コードは C++11 のものであり、 C++11 以前の C++ を使用している場合、ミューテックスロックを行う処理は自作 (または他のライブラリを利用) する必要がある。
#include <mutex>
static std::mutex s_mutex;
void init() {
std::lock_guard<std::mutex> lock(s_mutex);
Singleton::getInstance();
}
解決策 3 : コンパイルオプション -fthreadsafe-statics を明示する
$ g++ -fthreadsafe-statics main.cpp Singleton.cpp
まとめ
スレッドセーフな Singleton クラスを作成するには
- 静的な初期化
- 初期化ルーチンの追加
- コンパイルオプション -fthreadsafe-statics を明示する
などの方法が有効である。
なお、本問題を解決する方法を調べると DCLP (ダブルチェックロッキングパターン) という方法に出くわすと思う。
しかし DCLP は全てのコンパイラで確実に機能する保証は無いらしく、使用しない方が良いと思う。