web-dev-qa-db-fra.com

Comment la mise en œuvre par Meyers d'un Singleton est-elle réellement un Singleton

J'ai beaucoup lu sur les singletons, quand ils doivent et ne doivent pas être utilisés, et comment les mettre en œuvre en toute sécurité. J'écris en C++ 11, et j'ai rencontré l'implémentation paresseuse paresseuse de Meyer d'un singleton, comme vu dans cette question.

Cette implémentation est:

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

Je comprends comment cela est à l'abri des autres questions ici sur SO, mais ce que je ne comprends pas, c'est comment il s'agit en fait d'un modèle singleton. J'ai implémenté des singletons dans d'autres langues, et ceux-ci finissent toujours par quelque chose comme cet exemple de 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;
        }
}

Quand je regarde ce deuxième exemple, il est très intuitif de voir comment il s'agit d'un singleton, car la classe contient une référence à une seule instance d'elle-même et ne renvoie que cette instance. Cependant, dans le premier exemple, je ne comprends pas comment cela empêche l'existence de deux instances de l'objet. Mes questions sont donc:

  1. Comment la première implémentation applique-t-elle un modèle singleton? Je suppose que cela a à voir avec le mot-clé statique, mais j'espère que quelqu'un pourra m'expliquer en profondeur ce qui se passe sous le capot.
  2. Entre ces deux styles d'implémentation, l'un est-il préférable à l'autre? Quels sont les avantages et inconvénients?

Merci pour toute aide,

42
lbrendanl

Il s'agit d'un singleton car static la durée de stockage d'une fonction locale signifie qu'une seule instance de cette section locale existe dans le programme.

Sous le capot, cela peut très grossièrement être considéré comme équivalent au C++ 98 suivant (et peut même être implémenté vaguement comme ceci par un compilateur):

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

Les bits de sécurité des threads compliquent un peu les choses, mais c'est essentiellement la même chose.

En regardant une implémentation réelle pour C++ 11, il existe une variable de garde pour chaque statique (comme le booléen ci-dessus), qui est également utilisée pour les barrières et fils. Regardez la sortie AMD64 de Clang pour:

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

L'assemblage AMD64 pour instance d'Ubuntu Clang 3.0 sur AMD64 à -O1 (avec la permission de http://gcc.godbolt.org/ est:

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

Vous pouvez voir qu'il fait référence à un garde global pour voir si l'initialisation est requise, utilise __cxa_guard_acquire, teste à nouveau l'initialisation, etc. Exactement dans presque tous les sens, comme la version que vous avez publiée sur Wikipedia, sauf en utilisant l'assemblage AMD64 et les symboles/disposition spécifiés dans Itanium ABI .

Notez que si vous exécutez ce test, vous devez donner à Singleton un constructeur non trivial afin qu'il ne soit pas un POD, sinon l'optimiseur se rendra compte qu'il est inutile de faire tout ce travail de garde/verrouillage.

44
Sean Middleditch
// Singleton.hpp
class Singleton {
public:
    static Singleton& Instance() {
        static Singleton S;
        return S;
    }

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

Cette implémentation est connue sous le nom de Singleton de Meyers. Scott Meyers dit:

"Cette approche est fondée sur la garantie de C++ que les objets statiques locaux sont initialisés lorsque la définition de l'objet est rencontrée pour la première fois lors d'un appel à cette fonction." ... "En prime, si vous n'appelez jamais une fonction émulant un objet statique non local, vous n'encourez jamais le coût de construction et de destruction de l'objet."

Lorsque vous appelez Singleton& s=Singleton::Instance() la première fois que l'objet est créé et que chaque appel suivant à Singleton::Instance() résulte avec le même objet renvoyé. Problème principal:


Une autre implémentation est appelée le fidèle qui fuit 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;

Deux problèmes:

  • des fuites, sauf si vous implémentez une version et assurez-vous de l'appeler (une fois)
  • pas sûr pour les threads
14
4pie0