Подтвердить что ты не робот

Каким образом реализация Майерсом Синглтона на самом деле является синглтон

Я много читал о синглетонах, когда их следует и не следует использовать, и как их безопасно применять. Я пишу на С++ 11 и натолкнулся на ленивую инициализованную Meyer реализацию синглтона, как видно из этого вопроса.

Эта реализация:

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

Когда я смотрю на этот второй пример, становится очень интуитивно понятно, как это синглтон, поскольку класс содержит ссылку на один экземпляр самого себя и только когда-либо возвращает этот экземпляр. Однако в первом примере я не понимаю, как это предотвращает существование двух экземпляров объекта. Итак, мои вопросы:

  1. Как в первой реализации применяется одноэлементный шаблон? Я предполагаю, что это связано с ключевым словом static, но я надеюсь, что кто-то сможет подробно объяснить мне, что происходит под капотом.
  2. Между этими двумя стилями реализации один предпочтительнее другого? Каковы плюсы и минусы?

Спасибо за любую помощь,

4b9b3361

Ответ 1

Это singleton, потому что static время хранения для функции local означает, что в программе существует только один экземпляр этого локального объекта.

Под капотом это можно очень грубо считать эквивалентным следующему С++ 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();
}

Биты безопасности потока делают его немного сложнее, но по сути это одно и то же.

Глядя на фактическую реализацию для С++ 11, для каждой статической (например, булевой) существует защитная переменная, которая также используется для барьеров и потоков. Посмотрите на выход Clang AMD64 для:

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

Сборник AMD64 для instance от Ubuntu Clang 3.0 на AMD64 на -O1 (с учетом 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, иначе оптимизатор поймет, что нет смысла делать все, что защищает/блокирует работу.

Ответ 2

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

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

Эта реализация известна как Singleton Meyers. Скотт Мейерс говорит:

"Этот подход основан на гарантии С++, что локальные статические объекты инициализируются при первом обнаружении объекта во время вызова этой функции."..." В качестве бонуса, если вы никогда не звоните функция, эмулирующая нелокальный статический объект, вы никогда не берете на себя стоимость построения и разрушения объекта.

Когда вы звоните Singleton& s=Singleton::Instance() первый раз, когда объект создается, и каждый следующий вызов Singleton::Instance() приводит к возврату того же объекта. Основная проблема:


Другая реализация называется надежным пропуском 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;

Два вопроса:

  • утечки, если вы не реализуете Release и не вызываете его (один раз)
  • небезопасный поток