私はシングルトンについて、いつ使うべきか、使うべきでないか、そしてそれらを安全に実装する方法についてたくさん読んでいます。私はC++ 11で書いており、Meyerの怠zyな初期化されたシングルトンの実装に遭遇しました この質問
この実装は次のとおりです。
static Singleton& instance()
{
static Singleton s;
return s;
}
私はこれがSOに関する他の質問からスレッドセーフであることを理解していますが、私が理解していないのは、これが実際にシングルトンパターンである方法です。私は他の言語でシングルトンを実装しましたが、これらは常に Wikipedia からのこの例のようなものになります:
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo() { }
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo .class){
if (instance == null) {
instance = new SingletonDemo ();
}
}
}
return instance;
}
}
この2番目の例を見ると、クラスがそれ自体の1つのインスタンスへの参照を保持し、そのインスタンスのみを返すため、これがシングルトンである方法は非常に直感的です。ただし、最初の例では、これによりオブジェクトの既存の2つのインスタンスがどのように防止されるか理解できません。だから私の質問は:
助けてくれてありがとう、
関数localのstatic
保存期間は、そのローカルのインスタンスが1つだけプログラムに存在することを意味するため、これはシングルトンです。
内部では、これは次のC++ 98と非常に大雑把に同等であると見なすことができます(コンパイラによってこのように漠然と実装されることさえあります)。
static bool __guard = false;
static char __storage[sizeof(Singleton)]; // also align it
Singleton& Instance() {
if (!__guard ) {
__guard = true;
new (__storage) Singleton();
}
return *reinterpret_cast<Singleton*>(__storage);
}
// called automatically when the process exits
void __destruct() {
if (__guard)
reinterpret_cast<Singleton*>(__storage)->~Singleton();
}
スレッドセーフティビットにより、少し複雑になりますが、本質的には同じことです。
C++ 11の実際の実装を見ると、(上記のブール値のような)静的ごとにguard変数があり、これもバリアに使用されますおよびスレッド。 ClangのAMD64出力を見てください:
Singleton& instance() {
static Singleton instance;
return instance;
}
-O1でのAMD64上のUbuntu Clang 3.0からのinstance
のAMD64アセンブリ( http://gcc.godbolt.org/ の提供:
instance(): # @instance()
pushq %rbp
movq %rsp, %rbp
movb guard variable for instance()::instance(%rip), %al
testb %al, %al
jne .LBB0_3
movl guard variable for instance()::instance, %edi
callq __cxa_guard_acquire
testl %eax, %eax
je .LBB0_3
movl instance()::instance, %edi
callq Singleton::Singleton()
movl guard variable for instance()::instance, %edi
callq __cxa_guard_release
.LBB0_3:
movl instance()::instance, %eax
popq %rbp
ret
初期化が必要かどうかを確認するためにグローバルガードを参照していることがわかります。__cxa_guard_acquire
、初期化の再テストなど。 AMD64アセンブリと Itanium ABI で指定されたシンボル/レイアウトを使用することを除いて、Wikipediaから投稿したバージョンのようなほぼすべての方法で。
そのテストを実行する場合は、Singleton
を非自明なコンストラクターに渡してPODにしないようにしてください。そうしないと、オプティマイザーはそのすべてのガード/ロック作業を行う意味がないことを認識します。
_// Singleton.hpp
class Singleton {
public:
static Singleton& Instance() {
static Singleton S;
return S;
}
private:
Singleton();
~Singleton();
};
_
この実装は、マイヤーズのシングルトンとして知られています。スコット・マイヤーズ言う:
「このアプローチは、その関数の呼び出し中にオブジェクトの定義が最初に見つかったときにローカルの静的オブジェクトが初期化されるというC++の保証に基づいています。」 ...「ボーナスとして、非ローカルの静的オブジェクトをエミュレートする関数を呼び出さなければ、オブジェクトの構築と破棄のコストは発生しません。」
オブジェクトが初めて作成されたときにSingleton& s=Singleton::Instance()
を呼び出すと、次にSingleton::Instance()
を呼び出すたびに同じオブジェクトが返されます。主な問題:
別の実装は、信頼できる漏洩シングルトンと呼ばれます。
_class Singleton {
public:
static Singleton& Instance() {
if (I == nullptr) { I = new Singleton(); }
return *I;
}
private:
Singleton();
~Singleton();
static Singleton* I;
};
// Singleton.cpp
Singleton* Singleton::I = 0;
_
2つの問題: