GetInstanceメソッドでインスタンス変数が静的変数として宣言されているシングルトンパターンの実装を見てきました。このような:
SomeBaseClass &SomeClass::GetInstance()
{
static SomeClass instance;
return instance;
}
このアプローチには、次のようなメリットがあります。
このアプローチのマイナス面は何ですか(これはあまりOOP風ではありません)これはスレッドセーフですか?
C++ 11では、スレッドセーフです。
§6.7[stmt.dcl] p4変数の初期化中に制御が同時に宣言に入る場合、同時実行は初期化の完了を待機するものとします。
C++ 03の場合:
1つの問題は、2つのシングルトンがあり、それらが建設中および破壊中に互いに使用しようとする場合です。
これを読んでください: C++静的初期化順序の問題を見つける
この問題のバリエーションは、シングルトンがグローバル変数のデストラクタからアクセスされる場合です。この状況では、シングルトンは確実に破棄されていますが、getメソッドは破棄されたオブジェクトへの参照を返します。
これを回避する方法はいくつかありますが、それらは厄介で、実行する価値がありません。グローバル変数のデストラクタからシングルトンにアクセスしないでください。
安全な定義ですが醜いです。
きちんと整理するためにいくつかの適切なマクロを追加できると確信しています
SomeBaseClass &SomeClass::GetInstance()
{
#ifdef _WIN32
Start Critical Section Here
#Elif defined(__GNUC__) && (__GNUC__ > 3)
// You are OK
#else
#error Add Critical Section for your platform
#endif
static SomeClass instance;
#ifdef _WIN32
END Critical Section Here
#endif
return instance;
}
図のようにスレッドセーフではありません。 C++言語はスレッドに対してサイレントであるため、言語からの固有の保証はありません。プラットフォーム同期プリミティブを使用する必要があります。 Win32 :: EnterCriticalSection()、アクセスを保護します。
あなたの特定のアプローチは問題がありますb/cコンパイラーは最初の呼び出しで静的instance
を初期化するためにいくつかの(スレッドセーフではない)コードを挿入します。ほとんどの場合、関数本体が実行を開始する前(したがって、任意の同期を呼び出すことができます。)
SomeClass
へのグローバル/静的メンバーポインターを使用し、同期されたブロック内で初期化すると、実装の問題が少なくなります。
#include <boost/shared_ptr.hpp>
namespace
{
//Could be implemented as private member of SomeClass instead..
boost::shared_ptr<SomeClass> g_instance;
}
SomeBaseClass &SomeClass::GetInstance()
{
//Synchronize me e.g. ::EnterCriticalSection()
if(g_instance == NULL)
g_instance = boost::shared_ptr<SomeClass>(new SomeClass());
//Unsynchronize me e.g. :::LeaveCriticalSection();
return *g_instance;
}
私はこれをコンパイルしていませんので、説明のみを目的としています。また、Boostライブラリを使用して、元の例と同じ(またはそこに近い)寿命を取得します。 std :: tr1(C++ 0x)を使用することもできます。
仕様によると、これはVC++でも機能するはずです。誰もがそれを知っていますか?
キーワードvolatileを追加するだけです。 msdnのドキュメントが正しい場合、ビジュアルC++コンパイラはミューテックスを生成するはずです。
SomeBaseClass &SomeClass::GetInstance()
{
static volatile SomeClass instance;
return instance;
}