С++ Синглтон дизайн
Недавно я столкнулся с реализацией/реализацией шаблона проектирования Singleton для С++. Это выглядело так (я принял это из примера реальной жизни):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
Из этого объявления я могу вывести, что поле экземпляра инициируется в куче. Это означает, что есть выделение памяти. Для меня совершенно непонятно, когда именно память будет освобождена? Или есть ошибка и утечка памяти? Кажется, что в реализации есть проблема.
Мой главный вопрос: как мне его реализовать правильно?
Ответы
Ответ 1
В 2008 году я представил реализацию c++ 98 шаблона проектирования Singleton, который лениво оценивается, гарантируется уничтожением, а не технически-поточно-безопасным:
Может ли кто-нибудь предоставить мне образец Singleton в c++?
Ниже приведена обновленная версия c++ 11 шаблона проектирования Singleton, который оценивается с леними, правильно уничтожен и потокобезопасен.
class S
{
public:
static S& getInstance()
{
static S instance; // Guaranteed to be destroyed.
// Instantiated on first use.
return instance;
}
private:
S() {} // Constructor? (the {} brackets) are needed here.
// C++ 03
// ========
// Don't forget to declare these two. You want to make sure they
// are unacceptable otherwise you may accidentally get copies of
// your singleton appearing.
S(S const&); // Don't Implement
void operator=(S const&); // Don't implement
// C++ 11
// =======
// We can use the better technique of deleting the methods
// we don't want.
public:
S(S const&) = delete;
void operator=(S const&) = delete;
// Note: Scott Meyers mentions in his Effective Modern
// C++ book, that deleted functions should generally
// be public as it results in better error messages
// due to the compilers behavior to check accessibility
// before deleted status
};
См. Эту статью о том, когда использовать синглтон: (не часто)
Синглтон: как его использовать
См. Две статьи о порядке инициализации и о том, как справиться:
Порядок инициализации статических переменных
Поиск статических задач инициализации c++
См. Эту статью, описывающую сроки жизни:
Каково время жизни статической переменной в функции c++?
См. Эту статью, в которой обсуждаются некоторые потоковые последствия для синглетонов:
Экземпляр Singleton, объявленный как статическая переменная метода GetInstance, является поточно-безопасным?
См. Эту статью, в которой объясняется, почему двойная проверка блокировки не будет работать на c++:
Каковы все распространенные неопределенные типы поведения, о которых должен знать программист c++?
Д-р Доббс: c++ и "Опасности блокировки с двойной проверкой": часть I
Ответ 2
Будучи Singleton, вы обычно не хотите, чтобы он был разрушен.
Он будет разорван и освобожден, когда программа завершится, что является нормальным желаемым поведением для одноэлементного. Если вы хотите явно очистить его, довольно легко добавить статический метод в класс, который позволит вам восстановить его в чистое состояние и перераспределить его в следующий раз, когда он будет использоваться, но что вне сферы действия "классический" синглтон.
Ответ 3
Вы можете избежать выделения памяти. Существует много вариантов, все из которых имеют проблемы в случае многопоточной среды.
Я предпочитаю такую реализацию (на самом деле, я не правильно сказал, что предпочитаю, потому что я избегаю синглетонов как можно больше):
class Singleton
{
private:
Singleton();
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
};
У него нет динамического распределения памяти.
Ответ 4
@Локи Астари ответ отлично.
Однако бывают случаи с несколькими статическими объектами, где вы должны быть в состоянии гарантировать, что синглтон не будет уничтожен, пока все ваши статические объекты, которые используют синглтон, больше не нуждаются в нем.
В этом случае std::shared_ptr
можно использовать, чтобы поддерживать одножильный режим для всех пользователей, даже когда статические деструкторы вызывают в конце программы:
class Singleton
{
public:
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static std::shared_ptr<Singleton> instance()
{
static std::shared_ptr<Singleton> s{new Singleton};
return s;
}
private:
Singleton() {}
};
Ответ 5
Другая альтернатива без выделения: создайте одноэлементный, скажем, класс C
, как вам это нужно:
singleton<C>()
используя
template <class X>
X& singleton()
{
static X x;
return x;
}
Ни тот, ни ответ Cătălin автоматически не потокобезопасны в текущем С++, но будут в С++ 0x.
Ответ 6
Решение в принятом ответе имеет существенный недостаток - деструктор для синглтона вызывается после того, как элемент управления покидает функцию main()
. На самом деле могут быть проблемы, когда некоторые зависимые объекты размещаются внутри main
.
Я столкнулся с этой проблемой, когда пытался ввести Singleton в приложении Qt. Я решил, что все мои диалоговые окна настройки должны быть Singletons, и принял шаблон выше. К сожалению, основной класс Qt QApplication
был размещен в стеке в main
функции, и Qt запрещает создавать/уничтожать диалоги, когда объект приложения недоступен.
Вот почему я предпочитаю кучу выделенных синглетонов. Я предоставляю явные методы init()
и term()
для всех синглетонов и вызываю их внутри main
. Таким образом, у меня есть полный контроль над порядком создания/уничтожения синглетонов, а также я гарантирую, что синглтоны будут созданы, независимо от того, вызвал ли кто-то getInstance()
или нет.
Ответ 7
Если вы хотите выделить объект в кучу, почему бы не использовать уникальный указатель. Память также будет освобождена, так как мы используем уникальный указатель.
class S
{
public:
static S& getInstance()
{
if( m_s.get() == 0 )
{
m_s.reset( new S() );
}
return *m_s;
}
private:
static std::unique_ptr<S> m_s;
S();
S(S const&); // Don't Implement
void operator=(S const&); // Don't implement
};
std::unique_ptr<S> S::m_s(0);
Ответ 8
Вот простая реализация.
#include <Windows.h>
#include <iostream>
using namespace std;
class SingletonClass {
public:
static SingletonClass* getInstance() {
return (!m_instanceSingleton) ?
m_instanceSingleton = new SingletonClass :
m_instanceSingleton;
}
private:
// private constructor and destructor
SingletonClass() { cout << "SingletonClass instance created!\n"; }
~SingletonClass() {}
// private copy constructor and assignment operator
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;
// Another object gets the reference of the first object!
SingletonClass *anotherSingleton;
anotherSingleton = anotherSingleton->getInstance();
cout << anotherSingleton << endl;
Sleep(5000);
return 0;
}
Создается только один объект, и эта ссылка на объект возвращается каждый раз.
SingletonClass instance created!
00915CB8
00915CB8
Здесь 00915CB8 - это ячейка памяти singleton Object, то же самое в течение всей программы, но (обычно!) различна при каждом запуске программы.
N.B. Это не безопасный поток. Вы должны обеспечить безопасность потока.
Ответ 9
Я не нашел 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>
Ответ 10
На самом деле это, вероятно, выделено из кучи, но без источников нет возможности узнать.
Типичная реализация (взятая из некоторого кода, который у меня есть в emacs уже):
Singleton * Singleton::getInstance() {
if (!instance) {
instance = new Singleton();
};
return instance;
};
... и полагайтесь на программу, выходящую из области видимости для очистки после нее.
Если вы работаете на платформе, где очистка должна выполняться вручную, я бы, вероятно, добавил ручную процедуру очистки.
Другая проблема с этим заключается в том, что он не является потокобезопасным. В многопоточной среде два потока могли бы пройти через "if", прежде чем у них будет возможность выделить новый экземпляр (так и будет). Это все еще не слишком большое дело, если вы все равно полагаетесь на завершение программы.
Ответ 11
Кто-нибудь упомянул std::call_once
и std::once_flag
?
Большинство других подходов, включая двойную проверку блокировки, нарушены.
Одной из основных проблем в реализации одноэлементного шаблона является безопасная инициализация. Единственный безопасный способ - защитить последовательность инициализации синхронизирующими барьерами. Но сами эти барьеры должны быть безопасно начаты. std::once_flag
- это механизм, гарантирующий безопасную инициализацию.
Ответ 12
В дополнение к другому обсуждению здесь, возможно, стоит отметить, что вы можете иметь глобальность, без ограничения использования одного экземпляра. Например, рассмотрим случай ссылки, считая что-то...
struct Store{
std::array<Something, 1024> data;
size_t get(size_t idx){ /* ... */ }
void incr_ref(size_t idx){ /* ... */}
void decr_ref(size_t idx){ /* ... */}
};
template<Store* store_p>
struct ItemRef{
size_t idx;
auto get(){ return store_p->get(idx); };
ItemRef() { store_p->incr_ref(idx); };
~ItemRef() { store_p->decr_ref(idx); };
};
Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances
Теперь где-то внутри функции (например, main
) вы можете сделать:
auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201);
Ссылкам refs не нужно сохранять указатель на соответствующий Store
, потому что эта информация предоставляется во время компиляции. Вам также не нужно беспокоиться о времени жизни Store
, потому что компилятор требует, чтобы он был глобальным. Если действительно существует только один экземпляр Store
, то в этом подходе нет накладных расходов; с более чем одним экземпляром, до компилятора, чтобы быть умным в генерации кода. Если необходимо, класс ItemRef
может быть даже сделан friend
из Store
(у вас могут быть шаблонные друзья!).
Если Store
сам является шаблоном, тогда все становится более беспорядочным, но все же можно использовать этот метод, возможно, путем реализации вспомогательного класса со следующей сигнатурой:
template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning
instances of ItemRef<Store_t, store_p>. */ };
Теперь пользователь может создать тип StoreWrapper
(и глобальный экземпляр) для каждого глобального экземпляра Store
и всегда обращаться к хранилищам через свой экземпляр оболочки (таким образом забывая о деталях gory параметров шаблона, необходимых для использования Store
).
Ответ 13
Речь идет об управлении жизненным временем объекта. Предположим, что у вас в вашем программном обеспечении больше, чем однопользовательских. И они зависят от Singleger Logger. Во время уничтожения приложения предположим, что другой одноэлементный объект использует Logger для регистрации шагов его уничтожения. Вы должны гарантировать, что Logger должен быть очищен последним. Поэтому, пожалуйста, также ознакомьтесь с этой статьей:
http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
Ответ 14
Здесь много ответов, и это было задано некоторое время назад, но я хотел бы добавить следующее. Благодарим Алана и Пола Эзуста. (Вступление к разработке шаблонов на С++ с Qt)
Шаблон Singleton является специализированным factory, используемым в ситуациях, когда вы хотите ограничить количество или тип созданных экземпляров. Метод CustomerFactory:: instance(), определенный ниже, является примером o singleton factory. Он создает объект, если это необходимо, но только в первый раз, когда этот метод вызывается. При последующих вызовах он всегда возвращает указатель на тот же объект.
CustomerFactory* CustomerFactory::instance(){
static CustomerFactory* retval = 0;
if (retval == 0) retval = new CustomerFactory(qApp); 1
return retval;
}
1 Обеспечивает очистку этого объекта и всех его дочерних элементов при выходе из QApplication.
При работе с объектами кучи важно не оставлять утечки памяти. Вы можете использовать отношения родитель-ребенок QObjects, чтобы помочь в этом отношении.
Ответ 15
На принятый ответ конструктор копии S был удален, поэтому следуйте очевидному варианту использования get get error:
S s = S.getInstance();
как вызывающая сторона может получить возвращаемое значение getInstance?
Ответ 16
Документ, который был связан с выше, описывает недостаток блокировки с двойной проверкой - это то, что компилятор может выделить память для объекта и установить указатель на адрес выделенной памяти до того, как был вызван конструктор объекта. В С++ довольно просто использовать распределители для распределения памяти вручную, а затем использовать конструктивный вызов для инициализации памяти. Используя эту оценку, двойная проверка блокировки работает очень хорошо.
Ответ 17
#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}
Пример:
class CCtrl
{
private:
CCtrl(void);
virtual ~CCtrl(void);
public:
INS(CCtrl);
Ответ 18
Простой одноэлементный класс, это должен быть ваш файл класса заголовка
#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H
class SingletonClass
{
public:
static SingletonClass* Instance()
{
static SingletonClass* instance = new SingletonClass();
return instance;
}
void Relocate(int X, int Y, int Z);
private:
SingletonClass();
~SingletonClass();
};
#define sSingletonClass SingletonClass::Instance()
#endif
Получите доступ к вашему синглтону следующим образом:
sSingletonClass->Relocate(1, 2, 5);
Ответ 19
Я думаю, что вы должны написать статическую функцию, в которой ваш статический объект удален.
Вы должны вызвать эту функцию, когда собираетесь закрыть приложение.
Это обеспечит утечку памяти.
Ответ 20
Как насчет использования нового размещения следующим образом:
class singleton
{
static singleton *s;
static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align
static singleton* getinstance()
{
if (s == null)
{
s = new(buffer) singleton;
}
return s;
}
};