C ++シングルトン設計パターン


736

最近、C ++のシングルトンデザインパターンの実現/実装にぶつかりました。次のようになっています(実際の例から採用しています)。

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

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

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



10
このホワイトペーパーでは、C ++でのスレッドセーフと共にシングルトンを実装する方法についてのすばらしい議論を見つけることができます。 aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf

106
@sbi-絶対のみを扱うシスのみ。シングルトンなしで問題の大部分を解決できますか?もちろんです。シングルトンは独自の問題を引き起こしますか?はい。ただし、デザインとはトレードオフを考慮し、アプローチのニュアンスを理解するためのものなので、正直に言ってそれが悪いとは言えません。
derekerdmann、2011

11
@derekerdmann:私はあなたがグローバル変数を決して必要としないとは言いませんでした(そして、あなたがそれを必要とするとき、シングルトンが時々より良いです)。私が言ったことは、それらはできるだけ使用されるべきではないということです。シングルトンを価値あるデザインパターンとして美化することは、それがハックであるというよりも、それを使用するのが良い印象を与え、コードを理解しにくく、維持しにくく、テストすることを困難にします。コメントを投稿したのはこのためです。これまでのところ、これと矛盾することはありません。
sbi

13
@sbi:あなたが言ったのは「それらを使わないで」です。後で変更した「できるだけ使用しないでください」というはるかに合理的な方法ではありません-確かに違いがわかります。
jwd 2011年

回答:


1106

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

次に、遅延評価され、正しく破棄され、スレッドセーフであるシングルトン設計パターンの更新された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++プログラマーが知っておくべき一般的な未定義の動作は何ですか?
ドブス博士:C ++とダブルチェックロックの危険:パートI


23
いい答えです。しかし、これはスレッドセーフではないことに注意しなければならないstackoverflow.com/questions/1661529/...
ヴァルナ

4
@zourtney:多くの人々はあなたが今したことを理解していません:)
Johann Gerell

4
@MaximYegorushkin:これが破壊されるときは非常に明確に定義されています(あいまいさはありません)。参照:stackoverflow.com/questions/246564/...
マーティンニューヨーク

3
What irks me most though is the run-time check of the hidden boolean in getInstance()これは、実装手法の前提です。それが生きていると仮定する必要はありません。stackoverflow.com/a/335746/14065を参照してください。状況が常に有効になるように強制することができます(よりオーバーヘッドが少ないSchwarz counter)。グローバル変数では、順序を強制しないため、(コンパイルユニット全体で)初期化順序に関する問題が多くなります。このモデルの利点は、1)遅延初期化です。2)注文を強制する機能(Schwarzは役立ちますが醜いです)。うんはget_instance()非常に醜いです。
マーティンヨーク

3
@kol:いいえ。通常のものではありません。初心者が何も考えずにコードをコピーして貼り付けても、通常のコードにならないからです。常にユースケースを確認し、代入演算子が期待どおりの動作をすることを確認してください。コードをコピーして貼り付けると、エラーが発生します。
マーティンヨーク

47

シングルトンであるため、通常、破壊されることは望ましくありません。

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


4
静的シングルトン*インスタンスで削除が明示的に呼び出されない場合でも、これは技術的にはメモリリークと見なされませんか?
Andrew Garrison、

7
これは、単なるグローバル変数の宣言にすぎません。
ilya n。

15
何かを正直に言うと...シングルトンに対する「メモリリーク」の懸念は完全に無関係です。分解の順序が重要なステートフルリソースがある場合、シングルトンは危険な場合があります。しかし、すべてのメモリは、プログラムの終了時にオペレーティングシステムによって完全に回復されます... 99.9%のケースで、この完全に学術的なポイントを無効にします。「メモリリーク」であるものとそうでないものとの間で文法を議論したい場合は問題ありませんが、これは実際の設計決定からの注意散漫であることを認識してください。
-jkerian

12
@jkerian:C ++コンテキストでのメモリリークと破棄は、メモリリークに関するものではありません。本当にそれは資源制御についてです。メモリをリークすると、デストロクターが呼び出されないため、オブジェクトに関連付けられているリソースが正しく解放されません。メモリはプログラミングを教えるときに使用する単純な例にすぎませんが、そこにははるかに複雑なリソースがあります。
マーティンヨーク

7
@Martin完全に同意します。唯一のリソースがメモリであっても、リークのリストを調べて「重要ではない」ものを除外する必要がある場合、プログラムでREALリークを見つけようとすると、問題が発生します。これらをすべてクリーンアップして、リークを報告するツールが問題のあるものだけを報告するようにすることをお勧めします。
イルカ

38

メモリの割り当てを回避できます。多くのバリアントがあり、すべてマルチスレッド環境の場合に問題があります。

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

class Singleton
{
private:
   Singleton();

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

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


3
場合によっては、この遅延初期化は従うべき理想的なパターンではありません。1つの例は、シングルトンのコンストラクターがヒープからメモリを割り当て、その割り当てを、たとえば組み込みシステムまたは他の厳しく制御された環境で予測可能にする場合です。シングルトンパターンが使用に最適なパターンである場合は、クラスの静的メンバーとしてインスタンスを作成することをお勧めします。
dma 2009年

3
多くの大規模なプログラム、特に動的ライブラリを使用するプログラムの場合。プリミティブではないグローバルオブジェクトまたは静的オブジェクトは、ライブラリのアンロード時の破壊の問題により、多くのプラットフォームでプログラムの終了時にsegfaults / crashesを引き起こす可能性があります。これは、多くのコーディング規約(Googleを含む)が重要な静的オブジェクトとグローバルオブジェクトの使用を禁止する理由の1つです。
obecalp 2009年

そのような実装の静的インスタンスには内部リンケージがあり、異なる翻訳単位に一意で独立したコピーがあるため、混乱して誤動作が発生するようです。しかし、私はそのような実装をたくさん見ました、何かが足りないのですか?
FaceBro 2017

背後のコンパイラが独自のコピーコンストラクタを使用する場合、ユーザーがこれを複数のオブジェクトに割り当てないようにするにはどうすればよいですか?
トニーTannous

19

@ロキアスタリの答えはすばらしい。

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

この場合、プログラムの最後に静的デストラクタが呼び出されている場合でも、すべてのユーザーがシングルトン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() {}
};

9

別の非割り当ての代替:C必要に応じて、たとえばクラスのシングルトンを作成します。

singleton<C>()

を使用して

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

これもCâtălinの答えも、現在のC ++では自動的にスレッドセーフではありませんが、C ++ 0xには含まれます。


現在gccの下では、スレッドセーフです(しばらくの間)。
マーティンヨーク

13
この設計の問題は、複数のライブラリで使用される場合です。各ライブラリには、そのライブラリが使用するシングルトンの独自のコピーがあります。したがって、それはもはやシングルトンではありません。
マーティンヨーク

6

回答の中に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>


1
デフォルトのコンストラクタを保護して「= default;」にするまで、C ++ 17でこれを機能させることができませんでした。
WFranczyk

6

受け入れられた回答のソリューションには重大な欠点がありmain()ます。シングルトンのデストラクタは、コントロールが関数を離れた後に呼び出されます。内部にいくつかの依存オブジェクトが割り当てられている場合、実際に問題が発生する可能性がありますmain

Qtアプリケーションにシングルトンを導入しようとしたときに、この問題に遭遇しました。私は、すべてのセットアップダイアログをシングルトンにする必要があると判断し、上記のパターンを採用しました。残念ながら、QtのメインクラスQApplicationmain関数のスタックに割り当てられており、Qtはアプリケーションオブジェクトが使用できない場合にダイアログの作成/破棄を禁止しています。

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


2
現在受け入れられている回答を参照している場合、最初のステートメントは間違っています。すべての静的ストレージ期間オブジェクトが破棄されるまで、デストラクタは呼び出されません。
マーティンヨーク

5

これは簡単な実装です。

#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はシングルトンオブジェクトのメモリロケーションで、プログラムの実行中は同じですが、(通常!)プログラムが実行されるたびに異なります。

注意これはスレッドセーフではありません。スレッドセーフを確保する必要があります。


5

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

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);

3
c ++ 11では非推奨。代わりにunique_ptrが推奨されます。cplusplus.com/reference/memory/auto_ptr cplusplus.com/reference/memory/unique_ptr
Andrew

2
これはスレッドセーフではありません。ベターは作るためにm_s地元staticのをgetInstance()、テストすることなく、すぐにそれを初期化します。
Galik

2

確かにヒープから割り当てられていますが、ソースがないと知る方法がありません。

典型的な実装(すでにemacsにあるいくつかのコードから取得)は次のようになります。

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

...そして、プログラムが範囲外になって後でクリーンアップすることに依存します。

クリーンアップを手動で行う必要があるプラットフォームで作業している場合は、おそらく手動クリーンアップルーチンを追加します。

この方法で行うことのもう1つの問題は、スレッドセーフではないことです。マルチスレッド環境では、2つのスレッドが「if」を通過する前に、どちらかが新しいインスタンスを割り当てる機会が得られます(両方ともそうなります)。とにかくクリーンアップをプログラムの終了に依存している場合、これはそれほど大きな問題ではありません。


インスタンス変数がクラスインスタンスへのポインタであることがわかるので、推測できます。
Artem Barger、

3
シングルトンを動的に割り当てる必要はありません。実際、上記の設計を使用して自動的に割り当てを解除する方法がないため、これは悪い考えです。スコープから外れるようにすると、デストラクタが呼び出されず、単に遅延します。
マーティンヨーク

atexit関数を使用して、割り当てを自動的に解除できます。それが私たちの仕事です(良いアイデアだとは言いません)
Joe

2

誰か言及std::call_onceしましたstd::once_flagか?他のほとんどのアプローチ(ダブルチェックロックを含む)は壊れています。

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


2

私は最近、EECSクラスでこのトピックを取り上げました。講義ノートを詳しく見たい場合は、 http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf

シングルトンクラスを正しく作成する方法は2つあります。

最初の方法:

例にあるのと同じように実装します。破壊に関しては、「シングルトンは通常、プログラムの実行期間中存続します。ほとんどのOSは、プログラムの終了時にメモリと他のほとんどのリソースを回復するため、これについて心配しないという議論があります。」

ただし、プログラムの終了時にクリーンアップすることをお勧めします。したがって、これを補助静的SingletonDestructorクラスで行い、それをシングルトンのフレンドとして宣言できます。

class Singleton {
public:
  static Singleton* get_instance();

  // disable copy/move -- this is a Singleton
  Singleton(const Singleton&) = delete;
  Singleton(Singleton&&) = delete;
  Singleton& operator=(const Singleton&) = delete;
  Singleton& operator=(Singleton&&) = delete;

  friend class Singleton_destroyer;

private:
  Singleton();  // no one else can create one
  ~Singleton(); // prevent accidental deletion

  static Singleton* ptr;
};

// auxiliary static object for destroying the memory of Singleton
class Singleton_destroyer {
public:
  ~Singleton_destroyer { delete Singleton::ptr; }
};

Singleton_destroyerはプログラムの起動時に作成され、「プログラムが終了すると、ランタイムライブラリのシャットダウンコード(リンカーによって挿入された)によってすべてのグローバル/静的オブジェクトが破棄されるため、the_destroyerが破棄され、そのデストラクタがシングルトンを削除して実行します。デストラクタ。」

二番目の方法

これは、C ++ウィザードのScott Meyersによって作成されたMeyersシングルトンと呼ばれます。単にget_instance()を別の方法で定義します。これで、ポインターメンバー変数を取り除くこともできます。

// public member function
static Singleton& Singleton::get_instance()
{
  static Singleton s;
  return s;
}

返される値は参照によるものであり、.代わりに構文を使用できるため、これは適切です->メンバー変数にアクセスするです。

「コンパイラーは、宣言ではなく最初に「s」を作成するコードを自動的に構築し、その後はプログラムの終了時に静的オブジェクトを削除します。」

また、マイヤーズシングルトンを使用すると、「終了時にオブジェクトが相互に依存している場合、非常に困難な状況に陥る可能性があります。シングルトンが他のオブジェクトに対していつ消えるのですか?しかし、単純なアプリケーションの場合、これはうまく機能します。」


1

ここでの他の議論に加えて、使用を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); 

Storeその情報はコンパイル時に提供されるので、refはそれぞれへのポインターを格納する必要はありません。またStore、コンパイラはグローバルである必要があるため、の寿命を心配する必要はありません。実際にインスタンスが1つしかない場合Store、このアプローチにはオーバーヘッドはありません。複数のインスタンスがある場合、コード生成について賢いのはコンパイラ次第です。必要に応じて、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)。


0

これはオブジェクトの寿命管理に関するものです。ソフトウェアにシングルトン以上のものがあるとします。ロガーシングルトンに依存しています。アプリケーションの破棄中に、別のシングルトンオブジェクトがLoggerを使用してその破棄手順を記録するとします。ロガーを最後にクリーンアップすることを保証する必要があります。したがって、このペーパーも確認してください。http//www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf


0

私の実装はGalikの実装に似ています。違いは、アプリケーションが終了して静的ポインターがクリーンアップされるまでメモリーを保持するのではなく、私の実装では共有ポインターが割り当てられたメモリーをクリーンアップできることです。

#pragma once

#include <memory>

template<typename T>
class Singleton
{
private:
  static std::weak_ptr<T> _singleton;
public:
  static std::shared_ptr<T> singleton()
  {
    std::shared_ptr<T> singleton = _singleton.lock();
    if (!singleton) 
    {
      singleton.reset(new T());
      _singleton = singleton;
    }

    return singleton;
  }
};

template<typename T>
std::weak_ptr<T> Singleton<T>::_singleton;

0

クラスの外側でインスタンスポインターを宣言しなかった以外は、コードは正しいです。静的変数の内部クラス宣言はC ++の宣言とは見なされませんが、C#Javaなどの他の言語では許可されています

class Singleton
{
   public:
       static Singleton* getInstance( );
   private:
       Singleton( );
       static Singleton* instance;
};
Singleton* Singleton::instance; //we need to declare outside because static variables are global

シングルトンインスタンスを手動で削除する必要がないことを知っておく必要があります。プログラム全体でそのオブジェクトが1つ必要なので、プログラムの実行が終了すると、自動的に割り当てが解除されます。


-1

上記にリンクされている論文では、オブジェクトのコンストラクタが呼び出される前に、コンパイラがオブジェクトにメモリを割り当て、割り当てられたメモリのアドレスにポインタを設定する可能性があるというダブルチェックロックの欠点について説明しています。C ++では、アロケータを使用してメモリを手動で割り当て、次に構成呼び出しを使用してメモリを初期化することは非常に簡単です。このアプローチを使用すると、ダブルチェックされたロックは問題なく機能します。


2
残念ながら違います。これは、世の中の最高のC ++開発者の何人かによって深く議論されてきました。C ++ 03では、ダブルチェックロックが壊れています。
マーティンヨーク

-1
#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

例:

   class CCtrl
    {
    private:
        CCtrl(void);
        virtual ~CCtrl(void);

    public:
        INS(CCtrl);

-1

単純なシングルトンクラス、これはヘッダークラ​​スファイルでなければなりません

#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H

class SingletonClass
{
    public:
        static SingletonClass* Instance()
        {
           static SingletonClass* instance = new SingletonClass();
           return instance;
        }

        void Relocate(int X, int Y, int Z);

    private:
        SingletonClass();
        ~SingletonClass();
};

#define sSingletonClass SingletonClass::Instance()

#endif

次のようにシングルトンにアクセスします。

sSingletonClass->Relocate(1, 2, 5);

-3

静的オブジェクトが削除される静的関数を書くべきだと思います。アプリケーションを閉じるときは、この関数を呼び出す必要があります。これにより、メモリリークが発生しなくなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.