C++ シングルトン設計パターン
-
06-07-2019 - |
質問
最近、私は C++ のシングルトン設計パターンの実現/実装に遭遇しました。これは次のようになります (実際の例から採用しました)。
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
この宣言から、インスタンス フィールドがヒープ上で開始されることが推測できます。つまり、メモリの割り当てがあるということです。私にとって完全に不明なのは、メモリの割り当てが正確にいつ解除されるのかということです。それともバグやメモリリークがあるのでしょうか?実装に問題があるようです。
私の主な質問は、それを正しい方法で実装するにはどうすればよいですか?
解決
2008 年に、私は遅延評価され、破壊が保証され、技術的にはスレッドセーフではないシングルトン設計パターンの C++98 実装を提供しました。
誰か C++ のシングルトンのサンプルを提供してもらえますか?
これは、遅延評価され、正しく破棄され、 スレッドセーフ.
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
他のヒント
シングルトンであるため、通常は破壊されたくない。
プログラムが終了すると取り壊され、割り当てが解除されます。これは、シングルトンにとって望ましい通常の動作です。明示的にクリーンアップできるようにしたい場合は、静的メソッドをクラスに追加してクリーンな状態に復元し、次回使用時に再割り当てできるようにするのはかなり簡単ですが、それはスコープの範囲外です<!> quot; classic <!> quot;シングルトン。
メモリの割り当てを回避できます。マルチスレッド環境の場合、すべての問題を抱える多くのバリアントがあります。
この種の実装を好みます(実際には、シングルトンを可能な限り避けるので、実際には望ましいとは言いません):
class Singleton
{
private:
Singleton();
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
};
動的メモリ割り当てはありません。
@Loki Astariの回答は素晴らしいです。
ただし、複数の静的オブジェクトでは、 singleton を使用するすべての静的オブジェクトが破壊されるまで singleton が破壊されないことを保証する必要がある場合があります。もう必要です。
この場合、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() {}
};
別の非割り当て代替:必要に応じて、クラスC
のシングルトンを作成します:
singleton<C>()
使用
template <class X>
X& singleton()
{
static X x;
return x;
}
これもC <!>#259; t <!>#259; linの答えも、現在のC ++では自動的にスレッドセーフですが、C ++ 0xで行われます。
受け入れられた答えの解決策には重大な欠点があります-コントロールがmain()
関数を離れた後にシングルトンのデストラクタが呼び出されます。一部の依存オブジェクトがmain
内に割り当てられている場合、実際には問題が発生する可能性があります。
Qtアプリケーションにシングルトンを導入しようとしたときに、この問題に遭遇しました。私はすべてのセットアップダイアログをシングルトンにする必要があると判断し、上記のパターンを採用しました。残念ながら、QtのメインクラスQApplication
はinit()
関数でスタックに割り当てられ、Qtはアプリケーションオブジェクトが利用できない場合にダイアログを作成/破棄することを禁止しています。
それが、ヒープに割り当てられたシングルトンを好む理由です。すべてのシングルトンに明示的なterm()
およびgetInstance()
メソッドを提供し、それらを<=>内で呼び出します。したがって、シングルトンの作成/破棄の順序を完全に制御できます。また、誰かが<=>を呼び出したかどうかに関係なく、シングルトンが作成されることを保証します。
オブジェクトをヒープに割り当てる場合は、一意のポインターを使用しないでください。一意のポインターを使用しているため、メモリの割り当ても解除されます。
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);
答えの中に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>
これは簡単な実装です。
#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。これはスレッドセーフではありません。スレッドセーフを確保する必要があります。
実際にはおそらくヒープから割り当てられますが、ソースがないと知る方法がありません。
典型的な実装(すでにemacsにあるいくつかのコードから取得)は次のようになります。
Singleton * Singleton::getInstance() {
if (!instance) {
instance = new Singleton();
};
return instance;
};
...その後、クリーンアップするためにプログラムがスコープ外に出ることに依存しています。
クリーンアップを手動で行う必要があるプラットフォームで作業している場合は、おそらく手動のクリーンアップルーチンを追加します。
この方法で行う別の問題は、スレッドセーフではないことです。マルチスレッド環境では、2つのスレッドが<!> quot; if <!> quot;どちらかが新しいインスタンスを割り当てる機会がある前に(したがって両方に)。とにかくクリーンアップするためにプログラムの終了に依存している場合、これはまだ大したことではありません。
誰かがstd::call_once
とstd::once_flag
に言及しましたか?
ダブルチェックロックを含む他のほとんどのアプローチは壊れています。
シングルトンパターンの実装における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);
refは、情報がコンパイル時に提供されるため、それぞれのStore
へのポインタを格納する必要はありません。また、コンパイラはグローバルである必要があるため、ItemRef
の有効期間について心配する必要はありません。実際にfriend
のインスタンスが1つしかない場合、このアプローチにはオーバーヘッドはありません。複数のインスタンスを使用する場合、コード生成について賢くするのはコンパイラ次第です。必要に応じて、StoreWrapper
クラスを<=> of <=>にすることもできます(テンプレート化された友人を作成できます!)。
<=>自体がテンプレートクラスである場合、状況はより複雑になりますが、おそらく次のシグネチャを持つヘルパークラスを実装することにより、このメソッドを使用することも可能です:
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>. */ };
ユーザーは、各グローバル<=>インスタンスに対して<=>タイプ(およびグローバルインスタンス)を作成し、ラッパーインスタンスを介して常にストアにアクセスできるようになりました(したがって、< =>)。
これはオブジェクトのライフタイム管理に関するものです。ソフトウェアにシングルトン以上のものがあるとします。そして、それらはLoggerシングルトンに依存しています。アプリケーションの破壊中に、別のシングルトンオブジェクトがLoggerを使用して破壊ステップを記録するとします。ロガーは最後にクリーンアップする必要があることを保証する必要があります。したがって、このペーパーもチェックしてください。 http://www.cs.wustl.edu/~schmidt/PDF/ ObjMan.pdf
上記にリンクされた論文では、ダブルチェックロックの欠点は、オブジェクトのコンストラクターが呼び出される前に、コンパイラーがオブジェクトにメモリーを割り当て、割り当てられたメモリーのアドレスにポインターを設定できることです。ただし、c ++では、アロケーターを使用してメモリを手動で割り当ててから、コンストラクト呼び出しを使用してメモリを初期化するのは非常に簡単です。この方法を使用すると、二重チェックロックは正常に機能します。
#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);
単純なシングルトン クラス。これはヘッダー クラス ファイルでなければなりません
#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);
静的オブジェクトを削除する静的関数を作成する必要があると思います。 アプリケーションを閉じようとしているときに、この関数を呼び出す必要があります。 これにより、メモリリークを防ぐことができます。
このような新しいプレースメントの使用方法:
class singleton
{
static singleton *s;
static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align
static singleton* getinstance()
{
if (s == null)
{
s = new(buffer) singleton;
}
return s;
}
};