Содержание
Паттерн «одиночка» (Singleton, синглетон, синглет) относится к числу порождающих паттернов проектирования, то есть реализует один из подходов к созданию объекта.
Singleton — это класс, который гарантирует, что существует один и только один экземпляр класса и предоставляет глобальную точку доступа к нему.
Этот экземпляр создается синглетоном «за кулисами», поэтому клиенту класса нет необходимости его создавать. Экземпляр создается только тогда, когда в нем появляется потребность. За соблюдение этого ограничения отвечает класс-одиночка, а не класс-клиент.
К этому единственному экземпляру можно обращаться напрямую через глобальную точку доступа, представляющую собой статический метод, возвращающий этот объект. Если к моменту вызова экземпляр не создан, то он создается. Если же на момент вызова экземпляр существует, то этот метод возвращает его. Это называется ленивой или отложенной инициализацией (lazy initialization).
Назначение Singleton в следующем:
Также паттерн Singleton можно использовать, когда объект должен быть доступен для создания подклассов и клиентам необходимо использовать расширенный класс без изменения своего кода.
Мотивациями к применению паттерна Singleton — примеры из реального мира, которые в своей системе существуют только в одном экземпляре, например:
Класс-одиночка содержит как минимум:
Достоинства | Недостатки |
|
|
Существует несколько реализаций паттерна Singleton. Мы рассмотрим классическую реализацию, реализацию Майерса и улучшенную версию классической реализации.
Классическая версия паттерна Singleton была представлена в 1994 году в книге Design Patterns. Elements of Reusable Object-Oriented Software (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес).
Большая четверка (авторы книги)
Интерфейс
Singleton.h
class Singleton { public: static Singleton* getInstance(); protected: Singleton(); private: static Singleton* _instance; };
Реализация
Singleton.cpp
Singleton* Singleton::_instance = 0; Singleton* Singleton::getInstance () { if (_instance == 0) { _instance = new Singleton; } return _instance; }
В классической версии шаблона реализован защищенный конструктор, приватное статическое поле для указателя и статический метод для его получения. При первом обращении этот метод динамически выделяет нужный объем памяти и возвращает указатель на него.
Ниже приведена более новая версия этой реализации (для нее требуется C++11). В этой версии явно запрещается несколько операций:
#include <iostream> class ClassicSingleton{ private: static ClassicSingleton* _instance; ClassicSingleton() = default; ~ClassicSingleton() = default; public: ClassicSingleton(const ClassicSingleton&) = delete; ClassicSingleton& operator=(const ClassicSingleton&) = delete; static ClassicSingleton* getInstance(){ if ( !_instance ){ _instance = new ClassicSingleton(); } return _instance; } }; ClassicSingleton* ClassicSingleton::_instance = nullptr; int main(){ std::cout << std::endl << "Classic Singleton Demo" << std::endl; std::cout << "1st getInstance() call: "<< ClassicSingleton::getInstance() << std::endl; std::cout << "2nd getInstance() call: "<< ClassicSingleton::getInstance() << std::endl; std::cout << std::endl; std::cin; }
Вывод программы показывает, что существует лишь один экземпляр класса ClassicSingleton
:
В C++17 объявление и определение статической переменной экземпляра можно дать непосредственно в классе:
#include <iostream> class ClassicSingleton{ private: inline static ClassicSingleton* instance{nullptr}; ClassicSingleton() = default; ~ClassicSingleton() = default; public: ClassicSingleton(const ClassicSingleton&) = delete; ClassicSingleton& operator=(const ClassicSingleton&) = delete; static ClassicSingleton* getInstance(){ if ( !instance ){ instance = new ClassicSingleton(); } return instance; } };
Поскольку в классической реализации возвращается указатель на экземпляр класса, ответственность за очистку памяти несет пользователь. Чтобы избежать проблем с освобождением памяти, Скотт Майерс предложил другую реализацию паттерна Singleton.
Скотт Майерс
При завершении программы C++ автоматически уничтожает статические объекты. Можно воспользоваться этим, чтобы класс сам отвечал за освобождение памяти, отведенной для экземпляра. К тому же, эта реализация автоматически поддерживает многопоточность.
Статические переменные в локальной области видимости создаются при первом использовании. Такая отложенная инициализация — это гарантия, предоставляемая C++98. Singleton Майерса основан именно на этой идее. Вместо статического экземпляра класса Singleton в нем есть локальная статическая переменная с типом Singleton:
class MeyersSingleton{ private: MeyersSingleton() = default; ~MeyersSingleton() = default; public: MeyersSingleton(const MeyersSingleton&) = delete; MeyersSingleton& operator = (const MeyersSingleton&) = delete; static MeyersSingleton& getInstance(){ static MeyersSingleton instance; return instance; } };
Но и у реализации Мэйерса есть недостатки. Во-первых, с использованием этой реализации сложно создавать объекты производных классов. Во-вторых, порядок удаления синглетонов остается неопределенным.
Если вам потребуется контроль над удалением синглетонов, то его можно обеспечить с помощью дополнительного класса. Этот класс будет отвечать только за разрушение синглетона и у него будет доступ к деструктору. Этот способ предложил Джон Влиссидес (один из «банды четверых»).
Джон Влиссидес
Класс Singleton можно определить так:
class Singleton { public: static Singleton *Instance(); protected: Singleton(){} friend class SingletonDestroyer; virtual ~Singleton(){} private: static Singleton *_instance; static SingletonDestroyer _destroyer; }; Singleton *Singleton::_instance = 0; SingletonDestroyer Singleton::_destroyer; Singleton *Singleton::Instance() { if (!_instance) { _instance = new Singleton; _destroyer.SetSingleton(_instance); } return _instance; }
А вот код его разрушителя:
class SingletonDestroyer { public: SingletonDestroyer(Singleton * = 0); ~SingletonDestroyer(); void SetSingleton(Singleton *s); private: Singleton *_singleton; }; SingletonDestroyer::SingletonDestroyer(Singleton *s) { _singleton = s; } SingletonDestroyer::~SingletonDestroyer() { delete _singleton; } void SingletonDestroyer::SetSingleton(Singleton *s) { _singleton = s; }
В классе Singleton объявлен статический член SingletonDestroyer
, который автоматически создается при запуске программы. Если и когда пользователь впервые вызывает Singleton::Instance
, объект Singleton
создается и передается статическому объекту SingletonDestroyer
. Таким образом SingletonDestroyer
становится владельцем объекта.
Во время выхода из программы будет разрушен SingletonDestroyer, а вместе с ним и Singleton. Теперь разрушение Singleton производится неявно.
Обратите внимание на то, что SingletonDestroyer
объявлен в классе синглетона как friend
. Это сделано для предоставления доступа к защищенному деструктору класса Singleton.
Проблемы порядка создания и разрушения экземпляров приобретают особый вес, когда в программе используется несколько синглетонов. Выше мы рассмотрели один из способов решения вопроса о разрушении объекта. В этом разделе мы приводим код, в котором реализовано управление порядком создания объектов.
TwoSingletons.h
class SingletonAuto { private: SingletonAuto() { } SingletonAuto( const SingletonAuto& ); SingletonAuto& operator=( SingletonAuto& ); public: static SingletonAuto& getInstance() { static SingletonAuto instance; return instance; } }; class SingletonMain { private: SingletonMain( SingletonAuto& instance): s1( instance) { } SingletonMain( const SingletonMain& ); SingletonMain& operator=( SingletonMain& ); SingletonAuto& s1; public: static SingletonMain& getInstance() { static SingletonMain instance( SingletonAuto::getInstance()); return instance; } };
TwoSingletons.cpp
#include "TwoSingletones.h" int main() { SingletonMain& s = SingletonMain::getInstance(); return 0; }
Синглетон SingletonAuto
создается автоматически во время инициализации SingletonMain
путем вызова SingletonAuto::getInstance()
.
Паттерн Singleton нужно использовать только тогда, когда в нем есть необходимость. Помните, что синглетон призван представлять объект, который является единственным в своей системе.
А вообще синглетонов рекомендуется избегать. Причина в том, что это по сути замаскированные сложные глобальные объекты. Существует много реализаций синглетонов, и это тоже проблема.
Если вы не хотите, чтобы глобальный объект изменялся, то объявите его как const
или constexpr
.
Но существует и исключение: используйте простейший синглетон (настолько простой, что его часто можно не считать синглетоном), чтобы произвести инициализацию при первом использовании, если такая ситуация вообще складывается:
X& myX() { static X my_x {3}; return my_x; }
Это одно из самых лучших решений проблемы порядка инициализации. В многопотоковой среде инициализация статического объекта не приводит к состоянию гонки (если только не производится доступ к общедоступному объекту из его конструктора).
Но если уничтожение X подразумевает операцию, которую нужно синхронизировать, то нужно использовать менее простое решение. Например, такое:
X& myX() { static auto p = new X {3}; return *p; // potential leak }
Теперь клиенту нужно удалить этот объект, ориентируясь на потокобезопасность. Это может привести к ошибкам, поэтому не пользуйтесь таким способом, кроме случаев, когда:
myX
находится в многопотоковом коде;X
должен быть уничтожен (например, для освобождения ресурса);X
нужно синхронизировать.В заключение приведем рекомендации по очистке кода от ненужных синглетонов. Такая очистка — задача сложная, но выполнимая:
singleton
;Как и другие шаблоны, Singleton нужно применять именно тогда, когда он необходим. При правильном использовании этот паттерн может значительно повысить производительность и снизить затраты ресурсов в вашем приложении.
Чтобы закрепить материал, рекомендуем посмотреть полезные видео про использование Singleton в трех популярных языках программирования:
С++:
Java:
Python:
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…