web-dev-qa-db-ja.com

マイヤーズのシングルトンの実装は実際にはシングルトンであるか

私はシングルトンについて、いつ使うべきか、使うべきでないか、そしてそれらを安全に実装する方法についてたくさん読んでいます。私は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つのインスタンスがどのように防止されるか理解できません。だから私の質問は:

  1. 最初の実装では、どのようにシングルトンパターンが強制されますか? staticキーワードに関係していると思いますが、内部で何が起こっているのかを誰かが詳細に説明できることを望んでいます。
  2. これら2つの実装スタイルの間で、一方が他方よりも望ましいですか?長所と短所は何ですか?

助けてくれてありがとう、

42
lbrendanl

関数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にしないようにしてください。そうしないと、オプティマイザーはそのすべてのガード/ロック作業を行う意味がないことを認識します。

44
_// 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つの問題:

  • Releaseを実装し、必ず呼び出して(1回)しない限り、リーク
  • スレッドセーフではありません
14
4pie0