web-dev-qa-db-ja.com

C++シングルデザインパターン

最近私はC++のためのシングルトンデザインパターンの実現/実装にぶつかった。それはこのように見えました(私は現実の例からそれを採用しました):

// a lot of methods are omitted here
class Singleton
{
   public:
       static Singleton* getInstance( );
       ~Singleton( );
   private:
       Singleton( );
       static Singleton* instance;
};

この宣言から、インスタンスフィールドはヒープ上で開始されたと推測できます。つまり、メモリ割り当てがあるということです。私にとって全く不明確なのは、正確にメモリの割り当てが解除されるときです。それともバグやメモリリークはありますか?実装に問題があるようです。

私の主な質問は、どうやってそれを正しい方法で実装するのですか?

638
Artem Barger

2008年に、私は遅延評価され、破棄が保証され、技術的にスレッドセーフではない、SingletonデザインパターンのC++ 98実装を提供しました。
c ++でシングルトンのサンプルを提供してくれる人はいますか?

これは、遅延評価され、正しく破棄され、そして thread-safe である、Singleton設計パターンの更新されたC++ 11実装です。

class S
{
    public:
        static S& getInstance()
        {
            static S    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        S() {}                    // Constructor? (the {} brackets) are needed here.

        // C++ 03
        // ========
        // Don't forget to declare these two. You want to make sure they
        // are unacceptable otherwise you may accidentally get copies of
        // your singleton appearing.
        S(S const&);              // Don't Implement
        void operator=(S const&); // Don't implement

        // C++ 11
        // =======
        // We can use the better technique of deleting the methods
        // we don't want.
    public:
        S(S const&)               = delete;
        void operator=(S const&)  = delete;

        // Note: Scott Meyers mentions in his Effective Modern
        //       C++ book, that deleted functions should generally
        //       be public as it results in better error messages
        //       due to the compilers behavior to check accessibility
        //       before deleted status
};

シングルトンを使用する場合については、こちらの記事を参照してください。
シングルトン:どのように使われるべきですか

初期化の順序と対処方法については、この2つの記事を参照してください。
静的変数の初期化順序
C++の静的初期化順序の問題の発見

寿命について説明しているこの記事を参照してください。
C++関数の静的変数の寿命は?

シングルトンへのいくつかのスレッドの影響について説明しているこの記事を参照してください。
GetInstanceメソッドの静的変数として宣言されたシングルトンインスタンス、スレッドセーフですか?

ダブルチェックロックがC++で機能しない理由を説明したこの記事を参照してください。
C++プログラマーが知っておくべき一般的な未定義の動作は何ですか?
Dobbs博士:C++と二重チェック・ロッキングの危険:パートI

960
Martin York

シングルトンなので、通常は破壊されたくありません。

プログラムが終了すると、それは解体され、割り当て解除されます。これは、シングルトンにとって望ましい通常の動作です。明示的にクリーンにしたい場合は、クラスに静的メソッドを追加してクリーンな状態に戻し、次回使用するときに再割り当てさせることが非常に簡単ですが、これはの範囲外です。 「古典的な」シングルトン。

44
Reed Copsey

あなたはメモリ割り当てを避けることができます。多くの変種がありますが、すべてマルチスレッド環境の場合に問題があります。

私はこの種の実装を好みます(実際には、シングルトンをできるだけ避けているので、自分が好きとは言っていません)。

class Singleton
{
private:
   Singleton();

public:
   static Singleton& instance()
   {
      static Singleton INSTANCE;
      return INSTANCE;
   }
};

動的なメモリ割り当てはありません。

33

@Loki Astariさんの回答 優れています。

ただし、 シングルトン を使用するすべての静的オブジェクトが不要になるまで、 シングルトン が破棄されないことを保証できるようにする必要がある静的オブジェクトが複数ある場合があります。

この場合、std::shared_ptrを使用して、プログラムの最後で静的デストラクタが呼び出されている場合でも、すべてのユーザーに対して シングルトン を有効に保つことができます。

class Singleton
{
public:
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;

    static std::shared_ptr<Singleton> instance()
    {
        static std::shared_ptr<Singleton> s{new Singleton};
        return s;
    }

private:
    Singleton() {}
};
11
Galik

もう1つの割り当てられない代替方法:必要に応じて、クラスCなどのシングルトンを作成します。

singleton<C>()

使う

template <class X>
X& singleton()
{
    static X x;
    return x;
}

これもCătălinの答えも、現在のC++では自動的にスレッドセーフになりませんが、C++ 0xになるでしょう。

9
James Hopkin

受け入れられた答えの解決策は重大な欠点を持っています - シングルトンのためのデストラクタはコントロールがmain()関数を出た後に呼ばれます。いくつかの依存オブジェクトがmainの中に割り当てられている場合、本当に問題があるかもしれません。

Qtアプリケーションにシングルトンを導入しようとしたとき、私はこの問題に遭遇しました。私はすべての設定ダイアログはシングルトンでなければならないと決め、上記のパターンを採用しました。残念なことに、QtのメインクラスQApplicationmain関数内でスタック上に割り当てられていました、そしてQtはアプリケーションオブジェクトが利用できないときにダイアログの作成/破棄を禁じています。

それが、私がヒープ割り当てシングルトンを好む理由です。すべてのシングルトンに明示的なinit()メソッドとterm()メソッドを提供し、それらをmain内で呼び出します。したがって、私はシングルトンの作成/破棄の順序を完全に制御できます。また、誰かがgetInstance()を呼び出したかどうかに関係なく、シングルトンが作成されることを保証します。

6
SadSido

オブジェクトをヒープに割り当てたい場合は、ユニークなポインタを使用しないでください。私たちはユニークなポインタを使っているのでメモリも解放されます。

class S
{
    public:
        static S& getInstance()
        {
            if( m_s.get() == 0 )
            {
              m_s.reset( new S() );
            }
            return *m_s;
        }

    private:
        static std::unique_ptr<S> m_s;

        S();
        S(S const&);            // Don't Implement
        void operator=(S const&); // Don't implement
};

std::unique_ptr<S> S::m_s(0);
5
riderchap

これは簡単な実装です。

#include <Windows.h>
#include <iostream>

using namespace std;


class SingletonClass {

public:
    static SingletonClass* getInstance() {

    return (!m_instanceSingleton) ?
        m_instanceSingleton = new SingletonClass : 
        m_instanceSingleton;
    }

private:
    // private constructor and destructor
    SingletonClass() { cout << "SingletonClass instance created!\n"; }
    ~SingletonClass() {}

    // private copy constructor and assignment operator
    SingletonClass(const SingletonClass&);
    SingletonClass& operator=(const SingletonClass&);

    static SingletonClass *m_instanceSingleton;
};

SingletonClass* SingletonClass::m_instanceSingleton = nullptr;



int main(int argc, const char * argv[]) {

    SingletonClass *singleton;
    singleton = singleton->getInstance();
    cout << singleton << endl;

    // Another object gets the reference of the first object!
    SingletonClass *anotherSingleton;
    anotherSingleton = anotherSingleton->getInstance();
    cout << anotherSingleton << endl;

    Sleep(5000);

    return 0;
}

1つのオブジェクトだけが作成され、このオブジェクト参照はその後のたびに返されます。

SingletonClass instance created!
00915CB8
00915CB8

ここで、00915CB8はシングルトンオブジェクトのメモリ位置です。プログラムの期間は同じですが、プログラムが実行されるたびに(通常は)異なります。

N.B.これはスレッドセーフではありません。スレッドセーフを保証する必要があります。

答えの中にCRTPの実装が見つからなかったので、ここにあります。

template<typename HeirT>
class Singleton
{
public:
    Singleton() = delete;

    Singleton(const Singleton &) = delete;

    Singleton &operator=(const Singleton &) = delete;

    static HeirT &instance()
    {
        static HeirT instance;
        return instance;
    }
};

使用するには、このようにクラスを継承してください。class Test : public Singleton<Test>

3
Yuriy

それは確かにおそらくヒープから割り当てられますが、情報源がなければ知る方法はありません。

典型的な実装(私がすでにemacsに持っているいくつかのコードから取ったもの)は次のようになります。

Singleton * Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    };
    return instance;
};

...そして、後でクリーンアップするために範囲外に出るプログラムに頼ってください。

クリーンアップを手動で行わなければならないプラットフォームで作業する場合は、おそらく手動のクリーンアップルーチンを追加します。

このようにすることによるもう1つの問題は、スレッドセーフではないということです。マルチスレッド環境では、どちらかが新しいインスタンスを割り当てる機会がある前に、2つのスレッドが "if"を通過する可能性があります(両方ともそうなるでしょう)。あなたがとにかくクリーンアップするためにプログラムの終了に頼っているのであれば、これはまだ大したことではありません。

2
T.E.D.

誰もがstd::call_oncestd::once_flagに言及しましたか?ダブルチェックロッキングを含む他のほとんどのアプローチは破られています。

シングルトンパターンの実装における1つの大きな問題は安全な初期化です。唯一の安全な方法は、同期化バリアを使用して初期化シーケンスを保護することです。しかし、それらの障壁自体が安全に開始される必要があります。 std::once_flagは、安全な初期化を保証するためのメカニズムです。

2
Red.Wave

ここでの他の議論に加えて、使用法を1つのインスタンスに限定することなく、あなたがグローバル性を持つことができることは注目に値するかもしれません。たとえば、参照を数える場合を考えてみましょう。

struct Store{
   std::array<Something, 1024> data;
   size_t get(size_t idx){ /* ... */ }
   void incr_ref(size_t idx){ /* ... */}
   void decr_ref(size_t idx){ /* ... */}
};

template<Store* store_p>
struct ItemRef{
   size_t idx;
   auto get(){ return store_p->get(idx); };
   ItemRef() { store_p->incr_ref(idx); };
   ~ItemRef() { store_p->decr_ref(idx); };
};

Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances

mainなどの関数内のどこかで、次のことができます。

auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201); 

Refは、それぞれのStoreへのポインタを格納する必要はありません。その情報はコンパイル時に提供されるからです。 Storeの有効期間についても心配する必要はありません。コンパイラはそれがグローバルであることを要求するからです。実際にStoreのインスタンスが1つしかない場合は、このアプローチにオーバーヘッドはありません。複数のインスタンスを使用する場合、コード生成に関して賢いことはコンパイラの責任です。必要に応じて、ItemRefクラスをfriendStoreにすることもできます(テンプレート化された友達を持つことができます!)。

Store自体がテンプレート化されたクラスである場合、事態は複雑になりますが、おそらく以下のシグネチャを持つヘルパークラスを実装することによって、このメソッドを使用することはまだ可能です。

template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning 
                       instances of ItemRef<Store_t, store_p>. */ };

ユーザーは各グローバルStoreWrapperインスタンスに対してStore型(およびグローバルインスタンス)を作成し、常にそのラッパーインスタンスを介してストアにアクセスすることができます(したがって、Storeの使用に必要なテンプレートパラメーターの詳細については忘れています)。

1
dan-man

これはオブジェクトのライフタイム管理についてです。ソフトウェアにシングルトン以上のものがあるとします。そして彼らはロガーシングルトンに依存しています。アプリケーションの破棄中に、別のシングルトンオブジェクトがLoggerを使用してその破棄手順を記録するとします。あなたはロガーが最後にクリーンアップされるべきであることを保証しなければなりません。したがって、こちらの記事もチェックしてください。 http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf

0
baris.aydinoz