web-dev-qa-db-ja.com

C ++のシングルトンパターン

シングルトンパターンについて質問があります。

シングルトンクラスの静的メンバーに関する2つのケースを見ました。

まず、このようなオブジェクトです

class CMySingleton
{
public:
  static CMySingleton& Instance()
  {
    static CMySingleton singleton;
    return singleton;
  }

// Other non-static member functions
private:
  CMySingleton() {}                                  // Private constructor
  ~CMySingleton() {}
  CMySingleton(const CMySingleton&);                 // Prevent copy-construction
  CMySingleton& operator=(const CMySingleton&);      // Prevent assignment
};

1つはこのようなポインターです

class GlobalClass
{
    int m_value;
    static GlobalClass *s_instance;
    GlobalClass(int v = 0)
    {
        m_value = v;
    }
  public:
    int get_value()
    {
        return m_value;
    }
    void set_value(int v)
    {
        m_value = v;
    }
    static GlobalClass *instance()
    {
        if (!s_instance)
          s_instance = new GlobalClass;
        return s_instance;
    }
};

2つのケースの違いは何ですか?どちらが正しいか?

46
skydoor

おそらくAlexandrescuの本を読んでください。

ローカルスタティックに関しては、しばらくの間Visual Studioを使用していませんが、Visual Studio 2003でコンパイルする場合、DLLごとに1つのローカルスタティックが割り当てられました。デバッグの悪夢について話してください。ながら:/

1。シングルトンの寿命

シングルトンに関する主な問題は、ライフタイム管理です。

オブジェクトを使用しようとする場合は、生きていて蹴る必要があります。したがって、問題は初期化と破壊の両方に起因します。これは、グローバルでのC++の一般的な問題です。

通常、初期化は最も簡単に修正できます。両方の方法が示唆するように、最初の使用時に初期化するのに十分簡単です。

破壊はもう少し繊細です。グローバル変数は、作成された順序と逆の順序で破棄されます。ローカルの静的なケースでは、実際に物事を制御することはありません....

2。ローカルスタティック

_struct A
{
  A() { B::Instance(); C::Instance().call(); }
};

struct B
{
  ~B() { C::Instance().call(); }
  static B& Instance() { static B MI; return MI; }
};

struct C
{
  static C& Instance() { static C MI; return MI; }
  void call() {}
};

A globalA;
_

ここの問題は何ですか?コンストラクタとデストラクタが呼び出される順序を確認しましょう。

まず、構築フェーズ:

  • _A globalA;_が実行され、A::A()が呼び出されます
  • A::A()呼び出しB::B()
  • A::A()呼び出しC::C()

最初のアクセスでBおよびCインスタンスを初期化するため、問題なく動作します。

第二に、破壊段階:

  • C::~C()が呼び出されるのは、それが3つのうち最後に構築されたためです
  • B::~B()が呼び出されます...おっと、Cのインスタンスにアクセスしようとします!

したがって、破壊時の動作は未定義です。

。新しい戦略

ここでの考え方は簡単です。グローバルビルトインは他のグローバルの前に初期化されるため、作成したコードのいずれかが呼び出される前にポインターが_0_に設定され、テストが保証されます:

_S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; }
_

インスタンスが正しいかどうかを実際にチェックします。

しかし、言われているように、ここではメモリリークが発生し、最悪の場合は呼び出されないデストラクタが発生します。ソリューションが存在し、標準化されています。 atexit関数の呼び出しです。

atexit関数を使用すると、プログラムのシャットダウン中に実行するアクションを指定できます。それで、シングルトンを申し分なく書くことができます:

_// in s.hpp
class S
{
public:
  static S& Instance(); // already defined

private:
  static void CleanUp();

  S(); // later, because that's where the work takes place
  ~S() { /* anything ? */ }

  // not copyable
  S(S const&);
  S& operator=(S const&);

  static S* MInstance;
};

// in s.cpp
S* S::MInstance = 0;

S::S() { atexit(&CleanUp); }

S::CleanUp() { delete MInstance; MInstance = 0; } // Note the = 0 bit!!!
_

まず、atexitについて詳しく学習しましょう。シグネチャはint atexit(void (*function)(void));です。つまり、引数として何も受け取らず、何も返さない関数へのポインタを受け入れます。

第二に、それはどのように機能しますか?さて、前の使用例とまったく同じです。初期化時に、呼び出す関数へのポインターのスタックを構築し、破棄時に一度に1項目ずつスタックを空にします。したがって、実際には、関数は後入れ先出し方式で呼び出されます。

ここで何が起こるのでしょうか?

  • 最初のアクセス時の構築(初期化は問題ありません)、終了時間にCleanUpメソッドを登録します

  • 終了時間:CleanUpメソッドが呼び出されます。オブジェクトを破棄し(したがって、デストラクタで効果的に作業できます)、ポインタを_0_にリセットして通知します。

ABおよびCの例のように)すでに破壊されたオブジェクトのインスタンスを呼び出すとどうなりますか?さて、この場合、ポインターを_0_に戻したので、一時的なシングルトンを再構築し、サイクルが新たに始まります。スタックを削除しているので、長くは生きません。

Alexandrescuは、それが破壊された後に必要になった場合に灰から復活するため、それを_Phoenix Singleton_と呼びました。

別の代替方法は、静的フラグを設定し、クリーンアップ中にdestroyedに設定し、シングルトンのインスタンスを取得できなかったことをユーザーに通知することです(たとえば、nullポインターを返す)。ポインター(または参照)を返す際の唯一の問題は、その上でdeleteを呼び出すのに十分なバカな人がいないことを願うことです:/

4。モノイドパターン

Singletonについて話しているので、Monoidパターンを導入する時が来たと思います。本質的に、それはFlyweightパターンの縮退したケース、またはProxyに対するSingletonの使用と見なすことができます。

Monoidパターンは単純です:クラスのすべてのインスタンスは共通の状態を共有します。

この機会を利用して、Phoenixではない実装を公開します:)

_class Monoid
{
public:
  void foo() { if (State* i = Instance()) i->foo(); }
  void bar() { if (State* i = Instance()) i->bar(); }

private:
  struct State {};

  static State* Instance();
  static void CleanUp();

  static bool MDestroyed;
  static State* MInstance;
};

// .cpp
bool Monoid::MDestroyed = false;
State* Monoid::MInstance = 0;

State* Monoid::Instance()
{
  if (!MDestroyed && !MInstance)
  {
    MInstance = new State();
    atexit(&CleanUp);
  }
  return MInstance;
}

void Monoid::CleanUp()
{
  delete MInstance;
  MInstance = 0;
  MDestroyed = true;
}
_

利点は何ですか?状態が共有されているという事実を隠し、Singletonを隠します。

  • 2つの異なる状態が必要な場合は、使用するコードのすべての行を変更せずに(SingletonFactoryの呼び出しで置き換えて)例)
  • Nodobyはシングルトンのインスタンスでdeleteを呼び出すので、本当に状態を管理し、事故を防ぐことができます...とにかく悪意のあるユーザーに対しては大したことはできません!
  • シングルトンへのアクセスを制御します。そのため、シングルトンが破棄された後に呼び出された場合、正しく処理することができます(何もしない、ログ記録など)。

5。最後の単語

これは完全に思えるかもしれませんが、マルチスレッドの問題を喜んでスキミングしたことを指摘したいと思います...詳細については、AlexandrescuのModern C++をお読みください!

60
Matthieu M.

どちらも他よりも正確ではありません。私は一般的にシングルトンの使用を避けようとする傾向がありますが、それが道であると思うことに直面したとき、私はこれらの両方を使用し、それらはうまく働きました。

ポインターオプションの1つの問題は、メモリリークが発生することです。一方、最初の例は、完了する前に破壊される可能性があるため、このことに対してより適切な所有者を見つけようとしない場合でも、戦いを繰り広げることができます適切なタイミングで作成および破棄します。

4
dash-tom-bang

違いは、2番目のものはメモリ(シングルトン自体)をリークしますが、最初のものはメモリリークしません。静的オブジェクトは、関連付けられたメソッドが初めて呼び出されたときに初期化され、プログラムが正常に終了する限り、プログラムが終了する前に破棄されます。ポインターのあるバージョンでは、プログラムの終了時にポインターが割り当てられたままになり、Valgrindのようなメモリーチェッカーがエラーを出します。

また、誰かがdelete GlobalClass::instance();をするのを妨げているのは何ですか?

上記の2つの理由から、静的を使用するバージョンはより一般的な方法であり、元のデザインパターンブックで規定されている方法です。

2
Billy ONeal

2番目のアプローチを使用します-atexitを使用してオブジェクトを解放したくない場合は、常にkeeperオブジェクト(たとえば、auto_ptr、または自分で書いたもの)を使用できます。これにより、最初の最初の方法と同様に、オブジェクトを使用する前に解放が発生する場合があります。

違いは、静的オブジェクトを使用する場合、基本的に、すでに解放されているかどうかを確認する方法がないことです。

ポインターを使用する場合は、追加の静的ブールを追加して、シングルトンが既に破棄されているかどうかを示すことができます(モノイドのように)。そうすれば、コードはいつでもシングルトンが既に破壊されているかどうかを確認できます。意図したことで失敗する可能性はありますが、少なくとも不可解な「セグメンテーションフォールト」または「アクセス違反」は発生せず、プログラムは異常終了を回避します。

1
j_kubik

私はビリーに同意します。 2番目のアプローチでは、newを使用してヒープからメモリを動的に割り当てています。 deleteが呼び出されない限り、このメモリは常に残り、決して解放されません。したがって、グローバルポインターアプローチではメモリリークが発生します。

class singleton
{
    private:
        static singleton* single;
        singleton()
        {  }
        singleton(const singleton& obj)
        {  }

    public:
        static singleton* getInstance();
        ~singleton()
        {
            if(single != NULL)
            {
                single = NULL;
            }
        }
};

singleton* singleton :: single=NULL;
singleton* singleton :: getInstance()
{
    if(single == NULL)
    {
        single = new singleton;
    }
    return single;
}

int main() {
    singleton *ptrobj = singleton::getInstance();
    delete ptrobj;

    singleton::getInstance();
    delete singleton::getInstance();
    return 0;
}
1
Sagnik

より良いアプローチは、シングルトンクラスを作成することです。これにより、GetInstance()関数でのインスタンスの可用性チェックも回避されます。これは、関数ポインターを使用して実現できます。

class TSingleton;

typedef TSingleton* (*FuncPtr) (void);

class TSingleton {

TSingleton(); //prevent public object creation
TSingleton  (const TSingleton& pObject); // prevent copying object
static TSingleton* vObject; // single object of a class

static TSingleton* CreateInstance   (void);
static TSingleton* Instance     (void);
public:

static FuncPtr  GetInstance; 
};


FuncPtr TSingleton::GetInstance = CreateInstance;
TSingleton* TSingleton::vObject;

TSingleton::TSingleton()
{
}

TSingleton::TSingleton(const TSingleton& pObject)
{
}

TSingleton* TSingleton::CreateInstance(void)
{
if(vObject == NULL){

    // Introduce here some code for taking lock for thread safe creation
    //...
    //...
    //...

    if(vObject == NULL){

        vObject = new TSingleton();
        GetInstance = Instance;
    }
}

return vObject;
}

TSingleton* TSingleton::Instance(void)
{

return vObject;

}

void main()
{

TSingleton::GetInstance(); // this will call TSingleton::Createinstance()

TSingleton::GetInstance(); // this will call TSingleton::Instance()

// all further calls to TSingleton::GetInstance will call TSingleton::Instance() which simply returns already created object. 

}
0
Genie

最初の例は、シングルトンにとってより一般的です。 2番目の例は、オンデマンドで作成される点が異なります。

ただし、シングルトンはグローバル変数にすぎないため、一般にシングルトンの使用は避けようとします。

0
Skeets