2009-09-23 4 views
45

Сегодня мой друг спросил меня, почему он предпочитает использовать одноэлемент над глобальным статическим объектом? То, как я начал это объяснять, состоял в том, что синглтон может иметь состояние против статического глобального объекта, не будет ... но тогда я не был уверен ... потому что это на C++ .. (я шел из C#)C++ singleton vs. global static object

Каковы преимущества одного над другим? (в C++)

ответ

21

В C++ порядок экземпляра статических объектов в разных единицах компиляции не определен. Таким образом, один глобальный может ссылаться на другой, который не построен, взорвать вашу программу. Шаблон singleton устраняет эту проблему, связывая конструкцию со статической функцией-членом или свободной функцией.

Есть приличное резюме here.

+0

а затем вы вводите сложные условия гонки и отбрасываете безопасность потоков, поскольку два потока могут одновременно вызвать эту функцию. – jalf

+1

Как ни странно, std :: cout видит работу, несмотря на то, что это простой старый глобальный объект ... Я уверен, что порядок инициализации был такой большой проблемой.Это всего лишь проблема, если вы злоупотребляете глобальными шагами и зависеть от них друг от друга. – jalf

+1

@jalf: Сделайте домашнее задание. 'cout' и' cin' специально заданы для построения до 'main()' по стандарту. Другие глобальные объекты не получают этого лечения. Кроме того, существуют потокобезопасные одноэлементные реализации и нетрудно написать себе; вам нужен только двойной замок, чтобы предотвратить двойную конструкцию. Лично я стараюсь избегать одиночных игр, я думаю, что образец чрезмерно используется. Но ваш недостаток знаний на С ++ не является причиной для меня. – rlbond

52

Собственно, на C++ предпочтительным способом является локальный статический объект.

Printer & thePrinter() { 
    static Printer printer; 
    return printer; 
} 

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

Что делает его лучше, чем обычный способ делать одиночные игры с созданием нового экземпляра, вызывая new, это то, что объект-деструктор будет вызван в конце программы. Это не произойдет с динамически выделенным синглтоном.

Еще одна положительная сторона заключается в том, что невозможно получить доступ к синглетону до его создания даже из других статических методов или из подклассов. Сохраняет время отладки.

+7

Это лучший способ сделать одиночные игры. Использование нового не распространено в реальном коде, хотя в Интернете есть много примеров, которые, похоже, показывают, что это хорошая идея (не верьте им). –

+3

На самом деле, нет «лучшего» способа сделать одиночные игры. Если этот singleton ссылается на другой синглтон в своем деструкторе, он не будет работать должным образом. – rlbond

+2

Обратите внимание, что это, как правило, не является потокобезопасным (зависит от реализации: например, g ++ вставляет блокировки, но MSVC не будет). –

3

Причина 1:
Синглэты легко сделать так, чтобы они ленивы.
Хотя вы можете сделать это с помощью глобальных вычислений, разработчику требуется дополнительная работа. Поэтому по умолчанию глобальные переменные всегда инициализируются (кроме некоторых специальных правил с пространствами имен).

Так что если ваш объект большой и/или дорогой для сборки, вы, возможно, не захотите его строить, если вам действительно не нужно его использовать.

Причина 2:
Проблема с инициализацией (и уничтожением).

GlobalRes& getGlobalRes() 
{ 
    static GlobalRes instance; // Lazily initialized. 
    return instance; 
} 


GlobalResTwo& getGlobalResTwo() 
{ 
    static GlobalResTwo instance; // Lazy again. 
    return instance; 
} 


// Order of destruction problem. 
// The destructor of this object uses another global object so 
// the order of destruction is important. 
class GlobalResTwo 
{ 
    public: 
     GlobalResTwo() 
     { 
      getGlobalRes(); 
      // At this point globalRes is fully initialized. 
      // Because it is fully initialized before this object it will be destroyed 
      // after this object is destroyed (Guaranteed) 
     } 
     ~GlobalResTwo() 
     { 
      // It is safe to use globalRes because we know it will not be destroyed 
      // before this object. 
      getGlobalRes().doStuff(); 
     } 
}; 
2

Использование Singleton («построить при первом использовании») идиомы, вы можете избежать static initialization order fiasco

+1

Эта статья ужасна. Предлагаемое решение так же плохо, как и проблема. Используя новую функцию, вы никогда не вызовете деструктор. –

+1

Это одна из немногих ситуаций, когда утечки памяти в порядке, поскольку программа заканчивается в любом случае (если вы не находитесь на действительно ужасной ОС, которая каким-то образом не восстанавливает память). Если вы прочтете, следующий FAQ, он объяснит, почему. – coppro

+1

Прочтите разделы с 10.12 по 10.16 –

2

Еще одно преимущество Singleton над глобальным статическим объектом является то, что, поскольку конструктор является частным, есть очень ясно , принудительная директива компилятора, говорящая «может быть только одна».

Для сравнения с глобальным статическим объектом не будет ничего, что бы остановить код разработчика, создающий дополнительный экземпляр этого объекта.

Преимущество дополнительного ограничения заключается в том, что у вас есть гарантия относительно того, как будет использоваться объект.

+1

"компилятор принудительной директивы, говорящий« может быть только один ». Уход, чтобы придумать единый случай использования, где это ** хорошая ** вещь? Нецелесообразно добавлять произвольные и ненужные ограничения для вашего кода.Это полезно, если ваш код является * общим * и * повторно используемым *, и это не будет так, если первое, что вы делаете, это ограничить ограничения на него, которые вам не нужны. – jalf

+0

@ jalf: Большинство кодов должно быть общим и многоразовым. Но в большом приложении у вас может быть несколько объектов, требующих некоторой центральной ссылки - глобальной структуры приложения. Примером может быть какой-то менеджер блокировок. Различные части приложения, создающие свои собственные блокирующие менеджеры, будут неуместными. –

+0

Нет, у вас могут быть разные менеджеры блокировок для разных компонентов. – jalf

0

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

+0

Как будет реализован этот полиморфизм и каковы некоторые варианты использования? –

-1

ОК, есть две причины, чтобы пойти с синглом на самом деле. Один - это статический порядок, о котором все говорят.

Другое, чтобы предотвратить кого-то делать что-то вроде этого при использовании кода:

CoolThing blah; 
gs_coolGlobalStaticThing = blah; 

или, что еще хуже:

gs_coolGlobalStaticThing = {}; 

Аспект инкапсуляции защитит ваш экземпляр от идиотов и злонамеренных рывки.

+0

Вы говорите об аксессуарах чувака ... –

7

Мой друг сегодня спросил меня, почему он должен предпочесть использовать одноэлемент над глобальным статическим объектом? То, как я начал объяснять, было то, что у синглтона может быть состояние против статического глобального объекта, но не было ... но тогда я не был уверен ... потому что это на C++ .. (я шел из C#)

статический глобальный объект может иметь состояние в C#, а также:

class myclass { 
// can have state 
// ... 
public static myclass m = new myclass(); // globally accessible static instance, which can have state 
} 

Каковы преимущества один над другим? (в C++)

Одиночный кадр повреждает ваш код, глобальный статический экземпляр этого не делает. Есть бесчисленные вопросы о SO о проблемах с одиночными играми. Here's one, and another, or another.

Короче говоря, одноэлементно дает вам две вещи:

  • глобально доступный объект и
  • гарантию, что только один экземпляр может быть создан.

Если мы хотим только первую точку, мы должны создать глобально доступный объект. И почему мы когда-нибудь захотим второй? Мы не знаем заранее, как наш код может быть использован в будущем, так зачем гвоздь его и удалить, что может быть полезной функциональностью? Обычно мы ошибаемся, когда мы прогнозируем, что «мне нужен только один экземпляр». И есть большая разница между «Мне нужен только один экземпляр» (правильный ответ - создать один экземпляр), и «приложение ни при каких обстоятельствах не может работать правильно, если создано более одного экземпляра. сбой, форматирование жесткого диска пользователя и публикация конфиденциальных данных в Интернете »(ответ здесь:« Скорее всего, ваше приложение сломано, но , если это не так, то да, то, что вам нужно)

+0

Есть несколько законных целей для одноэлементного шаблона. Это чрезмерно, но есть случаи, когда все в порядке (Несколько примеров: глобальный RNG для игры. Объект опций конфигурации. Контроллер пула памяти.) – rlbond

+0

И, как я уже говорил выше, порядок построения объектов в разных единицах компиляции не определен , Вы не поверили мне и не вызвали «cout» и «cin», но это специальные случаи, определенные стандартом. – rlbond

+0

Существует небольшая причина, по которой в игре должен использоваться один глобальный RNG. Почему бы не уменьшить конкуренцию и не иметь одну нить? И вы даже можете захотеть другого в подсистеме. Или на игрока, в многопользовательском сценарии. Варианты конфигурации? Я легко вижу использование для двоих. У вас есть один набор активных конфигураций и второй набор, который пользователь в настоящее время модифицирует, но еще не применил. Вы можете ** легко ** иметь более одного пула памяти. Конечно, есть * законные цели использования одноэлементного шаблона. Но они крайне редки, и перечисленные вами * не * среди них. – jalf