目的:クラスのインスタンスを1つだけにすることを保証するとともに、
それにアクセスするための大域的な窓口を提供する。(GOF本からの引用)
目的:オブジェクトを1つだけ欲しいものの、そういったオブジェクト
の実体化を制御するような大域的なオブジェクトが存在しない。
また、このオブジェクトの同一インスタンスをすべての実体間で
参照を引き回すことなく使用させたい。
問題:複数のクライアントオブジェクトから同じものを参照するもの
必要があり、かつ、2つ以上の実体が生成されて欲しくない。
解決策:インスタンスの単一性を保証する。(すべてのオブジェクトが同じインスタンスを共有する)
C++でSingletonを実装する場合について考えて見ましょう。
ざっと思いつくだけで3通りくらいの実装方法があります。
(1) クラススコープのstatic変数とする場合
(2) 関数スコープのstatic変数とする場合
(3) new してヒープにつくる場合
やっかいなことに、どの実装方法がよいかはSingletonの使われ方によります。
Loggingというログ収集クラスを例にサンプルを作ってみましょう。
(1)の場合の実装はこんな感じでしょうか…
class Logging {
public:
static Logging& Instance();
...
void trace(...);
...
private:
Logging(); // Singletonにお決まりのコンストラクタ隠蔽
~Logging() {} // 勝手にデストラクタを呼ばれないように…
Logging(Logging&); // コピーコンストラクタも隠蔽すべし!
void operator = (Logging&);
static Logging singleton; // クラススコープのstatic変数にする場合
...
};
Logging Logging::singleton;
Logging& Logging::Instance() {
return singleton;
}
...
この場合、構築はプロセス起動時(ダイナミックリンクライブラリならライブラリロード時)におこなわれます。したがって、構築に関してはスレッドの衝突を考えなくてよいというメリットがあります。しかし、デメリットもあります。構築のタイミングをプログラマーが制御できないのです。別のコンパイル単位でつくられた他のSingletonやstatic変数のコンストラクタ内で上のsingletonが使われるとき、singletonの構築が完了していることを保障できないのです。
(2)の実装を見てみましょう…
class Logging {
public:
static Logging& Instance();
...
void trace(...);
...
private:
Logging(); // Singletonにお決まりのコンストラクタ隠蔽
~Logging() {} // 勝手にデストラクタを呼ばれないように…
Logging(Logging&); // コピーコンストラクタも隠蔽すべし!
void operator = (Logging&);
...
};
Logging& Logging::Instance() {
static Logging singleton; // この関数がはじめて呼ばれたときに構築される
return singleton;
}
...
この実装は初めてsingletonが使われるときに構築がおこなわれるため、他のstatic変数のコンストラクタから使用されても確実に構築が完了したsingletonを返すことができ、(1)のデメリットを克服しています。しかし、スレッドセーフティに関しては微妙です… 昔、VC++ 6.0で試したのですが、コンストラクタの中にSleep()をいれてわざと処理に時間がかかるコンストラクタをつくり、複数のスレッドからほぼ同時に Instance()関数を呼ばせたところ、いちばん最初にInstance()関数を呼んだスレッドのコンテキスト でコンストラクタが実行されたものの、他のスレッドはコンストラクタの処理が完了していないsingletonを受け取ってしまっていました。スレッドセーフにするためにミューテックス を使えばよいと思うかもしれませんが、ミューテックスの構築がいつおこなわれるのか考えると同じジレンマに陥ってしまいそうです…
(3)の実装はGoF 本にも書かれているやり方です…
class Logging {
public:
static Logging& Instance();
...
void trace(...);
...
private:
Logging(); // Singletonにお決まりのコンストラクタ隠蔽
~Logging() {} // 勝手にデストラクタを呼ばれないように…
Logging(Logging&); // コピーコンストラクタも隠蔽すべし!
void operator = (Logging&);
static Logging* singleton; // singletonを指すポインタ
...
};
Logging* Logging::singleton = 0;
Logging& Logging::Instance() {
if (0 == singleton) {
singleton = new Logging();
}
return *singleton;
}
...
最終更新:2009年06月07日 16:00