Как реализовать паттерн проектирования Singleton?
Недавно я столкнулся с реализацией шаблона проектирования Singleton для C++. Вот как это выглядит (я адаптировал это из примера из реальной жизни):
// здесь опущено множество методов
class Singleton
{
public:
static Singleton* getInstance();
~Singleton();
private:
Singleton();
static Singleton* instance;
};
Из этого объявления я могу сделать вывод, что поле instance
инициализируется в куче. Это означает, что происходит выделение памяти. Что совершенно непонятно для меня, так это когда именно эта память будет освобождена? Либо существует ошибка и утечка памяти? Кажется, что в реализации есть проблема.
Мой основной вопрос заключается в том, как правильно реализовать этот шаблон?
5 ответ(ов)
Вы можете избежать выделения памяти. Существуют различные варианты, и у всех из них есть проблемы в многопоточном окружении.
Лично я предпочитаю такой подход (на самом деле не совсем корректно говорить «предпочитаю», так как я стараюсь избегать синглтонов насколько это возможно):
class Singleton
{
private:
Singleton();
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
};
Этот вариант не использует динамическое выделение памяти.
Как правило, в паттерне Singleton вам не нужно, чтобы экземпляр класса уничтожался. Он будет разрушен и освобождён при завершении программы, что является нормальным и желаемым поведением для синглтона. Если вы хотите иметь возможность явно очищать экземпляр, можно легко добавить статический метод в класс, который позволит восстановить его в "чистое" состояние, а при следующем использовании — повторно выделить память. Однако это выходит за рамки "классического" синглтона.
Другой вариант без выделения памяти: создайте синглтон, скажем, класса C
, по мере необходимости:
singleton<C>()
с использованием следующего кода:
template <class X>
X& singleton()
{
static X x;
return x;
}
Ни одно из этих решений, включая ответ Кэтэлина, не является автоматически потокобезопасным в текущем C++, но станет таковым в C++0x.
Я не нашел реализации CRTP среди ответов, поэтому вот она:
template<typename HeirT>
class Singleton
{
public:
Singleton() = delete;
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
static HeirT &instance()
{
static HeirT instance;
return instance;
}
};
Чтобы использовать, просто унаследуйте свой класс от этого шаблона, например: class Test : public Singleton<Test>
.
Вот простой пример реализации паттерна Singleton на C++:
#include <Windows.h>
#include <iostream>
using namespace std;
class SingletonClass {
public:
static SingletonClass* getInstance() {
return (!m_instanceSingleton) ? m_instanceSingleton = new SingletonClass : m_instanceSingleton;
}
private:
// Закрытый конструктор и деструктор
SingletonClass() { cout << "Создан экземпляр SingletonClass!\n"; }
~SingletonClass() {}
// Закрытые конструктор копирования и оператор присваивания
SingletonClass(const SingletonClass&);
SingletonClass& operator=(const SingletonClass&);
static SingletonClass *m_instanceSingleton; // Статический указатель на единственный экземпляр
};
// Инициализация статического члена
SingletonClass* SingletonClass::m_instanceSingleton = nullptr;
int main(int argc, const char * argv[]) {
SingletonClass *singleton;
singleton = singleton->getInstance(); // Получение экземпляра
cout << singleton << endl;
// Другой объект получает ссылку на первый объект!
SingletonClass *anotherSingleton;
anotherSingleton = anotherSingleton->getInstance(); // Получение экземпляра снова
cout << anotherSingleton << endl;
Sleep(5000); // Задержка для наглядности
return 0;
}
Как вы можете видеть, только один объект создается, и этот объект возвращается каждый раз при вызове метода getInstance()
.
Создан экземпляр SingletonClass!
00915CB8
00915CB8
Здесь 00915CB8
— это адрес в памяти объекта Singleton, который остается неизменным на протяжении выполнения программы, но (обычно!) будет отличаться при каждом новом запуске программы.
Обратите внимание, что данная реализация не является потокобезопасной. Вам необходимо обеспечить потокобезопасность, если планируется использование в многопоточной среде.
В чем разница между #include <filename> и #include "filename"?
Когда использовать виртуальные деструкторы?
Какова разница между 'typedef' и 'using'?
Циклы в программном обеспечении для семейных деревьев
Что означает 'const' в конце объявления метода класса?