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

Синглтон - зачем использовать классы?

На днях я видел код, который использует так называемый шаблон singleton. Значит что-то вдоль линий

class MySingleton{
public:
    void foo() { ... }
    static MySingleton&get_instance(){
        static MySingleton singleton;
        return singleton
    }
private:
    MySingleton(){ ... }
    ~MySingleton(){ ... }
    int bar;
};

Я понимаю, почему нужно было бы это сделать:

  • Сделать экземпляр глобально доступным.
  • Убедитесь, что в этом классе не более одного экземпляра.

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

namespace some_name{
    void foo();
}

в заголовке и

namespace some_name{
    void foo(){
        ...
    }
}

в файле реализации. Если мне нужна инициализация и/или очистка, я либо добавляю пару функций, которые должны быть явно вызваны, либо добавляю

namespace{
    class Dummy{
        Dummy(){ ... }
        ~Dummy(){ ... }
    }dummy;
}

в файл реализации.

Я знаю, что это с семантической точки зрения синглтон, однако я вижу, что первый вариант используется гораздо чаще в коде С++, чем второй. Зачем? Я считаю, что вторая версия немного выше, поэтому я спрашиваю себя, не хватает ли я чего-то очевидного.

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

Не могли бы вы объяснить мне, почему каждый использует первый вариант? Я не вижу единственного преимущества над старым добрым способом делать вещи на C.

4b9b3361

Ответ 1

В соответствии с партийной линией (Э. Гамма, Р. Хелм, Р. Джонсон и Дж. Влиссидес. Шаблоны проектирования: элементы многоразового объектно-ориентированного Software. Addison-Wesley, Reading, MA, 1995, стр. 128), singleton предлагает следующие преимущества перед решением, которое вы предлагаете.

  • Вы можете уточнить операции и представление, например. через подклассы.
  • Вы можете изменить свое мнение в более поздней точке и иметь несколько экземпляров.
  • Полиморфно можно переопределить методы singleton.
  • Вы можете настроить приложение во время выполнения, инициализируя экземпляр singleton классом, который вам нужен.

Сказав это, в большинстве случаев я считаю излишнюю сложность чрезмерной и редко использую шаблон в коде, который я пишу. Но я вижу его ценность при разработке API, который другие будут использовать.

Ответ 3

Конструкция static MySingleton singleton; вызывается при первом использовании. (Когда вызывается get_instance().) Если он никогда не вызывается, он никогда не вызывает конструктор. Ваш метод вызовет конструктор при статическом времени построения. Предыдущий метод позволяет указать порядок и время вызова вызываемых конструкторов. Вы будете заказывать конструкцию каждого синглтона в соответствии со статическим порядком инициализации компилятора.

Ответ 4

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

И это не на самом деле не эмулирует поведение одноэлементного паттерна, а просто статического объекта, что не то же самое. Одноэлементное время жизни не зависит от жизни процесса, который его содержит. Правильно сформированный синглтон создается при первом использовании, тогда как статические данные создаются и инициализируются до запуска main(). Это может быть проблемой, если, например, построение объекта зависит от существования какого-либо другого объекта времени выполнения. Кроме того, объект singleton не занимает память (кроме ее статического указателя экземпляра) до момента ее создания. Он также может быть уничтожен и воссоздан в любое время и даже много раз.

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

Нет ничего плохого в вашем предложении как таковом, пока вы осознаете, что это не одноэлементный шаблон, и ему не хватает гибкости.

Ответ 5

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

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

Ответ 6

Лично я использую синглтоны, когда я хочу контролировать, когда конструктор выполняется в первый раз.

Например; если у меня есть singleton для моей лог-системы, код для открытия файла для записи помещается в singleton constructur, который называется like; Logger.instance(); в моем процессе запуска. Если бы я использовал пространство имен, у меня был бы этот контроль.