Содержание
Паттерн «одиночка» (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:
На фоне роста спроса на ликвидность в бычьем рынке 2025 года, криптозаймы снова выходят на…
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…