Страницы

Поиск по вопросам

пятница, 20 декабря 2019 г.

Зачем нам нужны синглтоны в С++?

#cpp #шаблоны_проектирования


Когда-то давно (лет 7 назад) читал я Александреску. И обратил внимание, что он много
места уделял созданию синглтонов. То есть таких типов, чтобы объекты этого типа существовали
в программе в единственном экземпляре.

Вопросы:


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


UPD1:


  [@pepsicoca1, поясни третью часть вопроса кодом... не совсем понятно,
  что именно ты имеешь ввиду.]


Уступая грубому нажиму публикую секретные протоколы синглтоностроения имени меня
(пример не транслировал, это импровизация):

class shell_singleton;

clacc singleton{
friend shell_singleton;

private:
void method1(){/*some action*/}
void method2(int a){/*some action*/}
void method3(int a,float b){/*some action*/}

public:
};

class shell_singleton{

static singleton singleton_var;

public:

void method1(){singleton_var.method1();}
void method2(int a){singleton_var.method2(a);}
void method3(int a,float b){singleton_var.method3(a,b);}
};

singleton shell_singleton::singleton_var;

void main(){
shell_singleton shell_singleton_var;

shell_singleton_var.method1();
shell_singleton_var.method2(3);
shell_singleton_var.method3(5,10.1);
}


В этом примере экземпляр shell_singleton::singleton_var; не может быть создан второй
раз. 

Попытка создания shell_singleton::singleton_var; приведет к ошибке компиляции или
линковки. 

Не нужны никакие счетчики создания экземпляров класса. 

Все решается средствами компилятора и линкера. 

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

Но это я перестраховался, проще пользователям не объявлять вообще о существовании
класса singleton, пусть работают с классом shell_singleton.


  [@pepsicoca1, поясни третью часть вопроса кодом


А по первым двум частям есть какие-то мысли? А то чего-то все гуру молчат, видать
никто не строит синглтоны, зря Александреску так распинался на эту тему. Неактуальная
тема видать.

На самом деле я задал этот вопрос еще в одном месте. Вот по этой ссылке можно поглядеть
обсуждение:

http://www.cyberforum.ru/cpp-beginners/thread2205986.html

И там, в отличие от стековерфлоу, было несколько ответов. Не скажу, чтобы доводы
коллег меня убедили в важности построения синглтонов . Но хоть что-то, хоть какие-то
версии.

UPD2:

Подумал тут на досуге. Ну вот создал программист синглтон. Типа защитил себя от ужасных
юзеров. А юзер взял и создал DLL и в этой DLL создал второй экземпляр типа синглтона.
Никакой компилятор это не отследит, так как DLL транслируется и линкуется отдельно.
И от чего вы тогда защитились синглтоном?
    


Ответы

Ответ 1



Процитирую уже упомянутый «Design Patterns»: Назначение Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа. От себя: А теперь по вопросам: А зачем нам вообще нужны такие объекты, которые существуют в программе в единственном экземпляре? Синглтон, как и все паттерны — это только инструмент, который упрощает жизнь, если его использовать по назначению. Так что ответ зачем — потому что так проще сделать и это решение лучше альтернатив (глобальные переменные и функции) в данной конкретной ситуации. Почему так происходит — потому что при реализации просто всплывают какие-то менеджеры, какие-то фабрики, какие-то киперы, которых по логике вещей не должно быть больше одного, но к которым нужен доступ из совершенно несвязанных частей кода. Первый пример, который приходит на ум — объект «Приложение» во всевозможных фреймворках. В чем проблема просто тупо создать объект указанного типа один раз в программе и больше не создавать объектов такого типа? Создать объект — не проблема, а вот получить на него ссылку из произвольного места программы — проблема (см. Назначение выше), её синглтон как раз и решает. ... Чем статический член любого класса не синглтон? По сути решение имеет право на жизнь и в каком-то абстрактном случае может быть предпочтительным, однако плюсов по сравнению с обычным синглтоном я не вижу, а из минусов: Отсутствует возможность расширения созданием подклассов. Бывает необходимость общаться не с самим сиглтоном, как таковым, а создать конкретный подкласс и общаться с ним через интерфейс базового, тогда структура получается примерно следующая: class Singleton { public: static Singleton instance(){ if(!_instance) { throw runtime_error("create a concrete singleton first ")} return _instance; } virtual Type doSomething()=0; protected: Singleton(); static Singleton* _instance; } class ConcreteSingleton: public Singleton { public: static ConcreteSingleton* create(T1 param1, T2 param2) { if(_instance) { throw runtime_error("a concrete singleton already exists")} _instance = new ConcreteSingleton (param1, param2); return _instance; } virtual Type doSomething(T1 param1, T2 param2); protected: ConcreteSingleton(); } Такое часто бывает, если синглтон является абстрактной фабрикой, задней частью моста или стратегией. У пользователя нет возможности как-либо повлиять на создание объекта. В частности, передать что-либо в create(), как в примере выше, нет возможности не создавать синглтон вообще, если он не нужен, нет возможности ленивой инициализации итп. Если есть несколько взаимодействующих синглтонов, то сложно задать порядок их создания. Усложняет жизнь всем т.к. при первом взгляде возникает вопрос «WTF?». Вводит лишнюю сущность.

Ответ 2



Давайте для начала вспомним, что такое синглтон. Синглтон — это тип, у которого за время жизни программы может быть создан лишь один экземпляр. Классическая книга Design Patterns описывает синглтон как класс без публичного конструктора, и со статическим методом get_instance(). В более широком смысле синглтоном можно назвать любой класс с одним экземпляром, независимо от имлементации. Теперь ответы на ваши вопросы. Очень просто. Классы должны моделировать объекты реальности. Например, если вы пишете программу, описывающую планету, то эта планета в рамках программы единственная, и её удобно описывать синглтоном. Если вы пишете программу, которая управляет пушкой, то экземпляр пушки в программе — вполне себе синглтон. Да, вы можете просто договориться с самим собой, и не создавать других экземпляров класса. Это нормально и работает до тех пор, пока в вашем проекте не появляется второй, третий, четвёртый разработчик. Они видят класс, и они не знают или просто не держат в голове, что он должен быть только в одном экземпляре. Поэтому они спокойно создают ещё экземпляр, и в результате в вашей Солнечной Системе в цикле генерируется по одному Солнцу каждую микросекунду. Не забывайте: людям свойственно допускать ошибки. Цена ошибки, которую находит компилятор, не давай скомпилировать неправильный код, намного ниже, чем цена ошибки, которая найдена тестерами или (намного хуже!) клиентами. Вы должны всегда исходить из того, что если что-то не запрещено компилятором, разработчик попытается это сделать. Этим разработчиком, кстати, можете оказаться и вы сами, если вы вернётесь доделывать этот проект после полугодичной паузы. Поэтому если компилятор можно заставить следить за выполнением какого-то правила, то так нужно и делать. Статический член не является синглтоном просто потому, что это не класс, у которого можно создать только один экземпляр. То есть, по определению синглтона. В более широком смысле, статический член можно считать некой заменой синглтона, но у него есть недостаток: программист может подменить экземпляр объекта, лежащего в статическом поле. А это как раз то, чего для синглтона не должно быть возможно.

Ответ 3



Для себя я рассматриваю небходимость использования данного паттерна так: Если вы хотите написать какой-то функционал, который необходимо использовать в разных частях программы - к примеру, загрузчик ресурсов, то вы можете использовать статические функции класса. Однако если вы хотите использовать в вашем загрузчике ресурсов функционал, определённый в каком-то другом классе, то вы, возможно, захотите использовать наследование. Тогда вам придётся унаследовать ResourceLoader от какого-либо класса с необходимым функционалом. А быть уверенным, что в программе существует единственный экземпляр загрузчика ресурсов, вам поможет синглтон. (Чтобы не допустить конкуренции за ресурсы, к примеру). Ещё одна важная особенность синглтонов - возможность лениво инициализировать это все дело. Может быть, вам никогда в программе и не понадобится загрузчик ресурсов. Тогда вы к нему не обратитесь и не будет создан экземпляр загрузчика, не будут лишний раз использоваться процессорные ресурсы (вдруг этот класс у вас индексирует всю файловую систему при создании, что не быстро). Что касается специального ключевого слова - в C++ его нет. Просто потому что синглтон - уже паттерн проектирования, нечто уровнем выше, чем тот функционал, который предлагает C++. Однако, в некоторых других, более высокоуровневых языках (как Kotlin или Scala) вообще нет понятия статический член, там эквивалентный функционал реализован с помощью объектов-компаньонов, которые являются синглтонами. И да, их можно наследовать! Вот как-то так. Конечно, программист сам выбирает, что и как ему использовать. Но, как сказал @VladD, зачем лишний раз держать что-то в голове, если можно заставить компилятор проверять все за вас?

Комментариев нет:

Отправить комментарий