54

シングルトンについて、使用すべき場合と使用すべきでない場合、および安全に実装する方法について多くのことを読んできました。私はC++ 11で書いていますが、この質問に見られるように、シングルトンのマイヤーの遅延初期化実装に出くわしました。

この実装は次のとおりです。

static Singleton& instance()
{
     static Singleton s;
     return s;
}

SOに関する他の質問から、これがスレッドセーフであることは理解していますが、理解できないのは、これが実際にシングルトンパターンである方法です。私は他の言語でシングルトンを実装しましたが、これらは常にウィキペディアの次の例のようになります。

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 つの実装スタイルのうち、どちらが望ましいでしょうか? 長所と短所は何ですか?

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

4

2 に答える 2

58

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 の実際の実装を見ると、バリアとスレッドにも使用される各静的 (上記のブール値のような)のガード変数があります。Clang の AMD64 出力を見てください。

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

instance-O1 の AMD64 上の Ubuntu の Clang 3.0 からの 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で指定されたシンボル/レイアウトを使用することを除いて、ウィキペディアから投稿したバージョンとまったく同じです。

そのテストを実行する場合は、Singleton重要なコンストラクターを指定して POD ではないことに注意してください。そうしないと、オプティマイザーはガード/ロック作業をすべて実行する意味がないことに気付きます。

于 2013-07-18T03:01:23.873 に答える
24
// Singleton.hpp
class Singleton {
public:
    static Singleton& Instance() {
        static Singleton S;
        return S;
    }

private:
    Singleton();
    ~Singleton();
};

This implementation is known as Meyers' Singleton. Scott Meyers says:

"This approach is founded on C++'s guarantee that local static objects are initialized when the object's definition is first encountered during a call to that function." ... "As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object."

When you call Singleton& s=Singleton::Instance() first time the object is created and every next call to Singleton::Instance() results with the same object being returned. Main issue:


Another implementation is called the trusty leaky Singleton.

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;

Two issues:

  • leaks, unless you implement a Release and make sure to call it (once)
  • not thread safe
于 2013-07-18T00:20:51.613 に答える