web-dev-qa-db-ja.com

GetInstanceメソッドの静的変数として宣言されたシングルトンインスタンス、スレッドセーフですか?

GetInstanceメソッドでインスタンス変数が静的変数として宣言されているシングルトンパターンの実装を見てきました。このような:

SomeBaseClass &SomeClass::GetInstance()
{
   static SomeClass instance;
   return instance;
}

このアプローチには、次のようなメリットがあります。

  • GetInstanceが初めて呼び出されたときにのみこのオブジェクトを作成するのはコンパイラーであるため、コードはより単純です。
  • インスタンスへの参照を取得する方法は他にないため、コードはより安全ですが、GetInstanceメソッドを使用し、インスタンスを変更する方法は他にありませんが、GetInstanceメソッド内です。

このアプローチのマイナス面は何ですか(これはあまりOOP風ではありません)これはスレッドセーフですか?

30
okutane

C++ 11では、スレッドセーフです。

§6.7[stmt.dcl] p4変数の初期化中に制御が同時に宣言に入る場合、同時実行は初期化の完了を待機するものとします。

C++ 03の場合:

  • G ++では、スレッドセーフです。
    しかし、これはg ++がそれを保証するコードを明示的に追加するためです。

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;
}
37
Martin York

図のようにスレッドセーフではありません。 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)を使用することもできます。

5
Henk

仕様によると、これはVC++でも機能するはずです。誰もがそれを知っていますか?

キーワードvolatileを追加するだけです。 msdnのドキュメントが正しい場合、ビジュアルC++コンパイラはミューテックスを生成するはずです。

SomeBaseClass &SomeClass::GetInstance()
{
   static volatile SomeClass instance;
   return instance;
}
1
Sal