Использование static для реализации паттерна singleton в c++
Здраствуйте. Я тут начал изучать паттерны и добрался до singleton'ов. Начал рассматривать примеры (все примерно такие:
struct singleton
{
static singleton& get_instance()
{
static singleton instance;
return instance;
}
singleton(singleton const&) = delete;
singleton(singleton&&) = delete;
int a = 0;
private:
singleton() { }
};
int main()
{
singleton& s = singleton::get_instance();
s.a = 42;
}
), и у меня назрел вопрос: зачем вообще работать с экземплярами класса, когда можно сделать через статику? Например,
struct Single{
Single(Single &&) = delete;
Single(const Single&) = delete;
static int a;
};
int Single::a = 0;
int main(int argc, char *argv[])
{
Single::a = 42;
}
Ответы (2 шт):
Все можно, но есть ряд вопросов.
а если Вашему классу нужно сложное создание/удаление (пример - подключение к базе данных)?
Да, можно сделать функцию Init, но тут сразу куча проблем - где именно вызывать, а если нужна ленивая загрузка, а если многопоточность.
а если Вашему классу нужна сложная внутренняя логика (ну не хочется, что бы два потока одновременно в базу много писали/читали)?
давайте попроще. Есть два синглтона - работа с базой и логгер. И первый использует второго (это очевидно, но можно и наоборот, и циклически). И расположены они в разных файлах (единицах компиляции). И вот тут легко споймать [static initialization order fiasco]https://isocpp.org/wiki/faq/ctors#static-init-order)
а если нужно наследоваться от какого то базового класса?
ну мало чего.
private static хотя и допустимо, но выглядит откровенно наивно и не прячет нормально внутренности класса (они как бы на виду, и одновременно спрятаны).
почти всегда, когда в коде есть приватная статическая штука, это либо ошибка при проектировании, либо это просто нужно переместить в cpp файл
Да и к Вашему классу есть ряд вопросов. К примеру, дефолтный конструктор Вы не трогали, операторы также, а перемещения и копирования - запретили. странно это.
и у меня назрел вопрос: зачем вообще работать с экземплярами класса, когда можно сделать через статику?
Экземпляр можно куда-то передать, вернуть из функции, дать его адрес другой библиотеке и т.д., т.е. у нас именно объект-одиночка, а не набор несвязных друг с другом переменных, собранных в пространстве класса.
Создание экземпляра можно контролировать, просто static-поля - нет. Для примера последнего посмотрим на код:
//one.h
struct MySingleton
{
//...
static int a;
};
//one.cpp
int MySingleton::a = 10;
//two.cpp
//...
int b = MySingleton::a; //static initialization order fiasco
В данном случае неизвестно, какая переменная будет инициализирована ранее - b в two.cpp или MySingleton::a в one.cpp, а значит у нас большие проблемы.
Также необходимо как-то реализовать правильную инициализацию всего этого добра, собранного в скоупе класса. Ваше предложение, -
ну можно где-нибудь вызвать статический метод init() / destroy()
это двухэтапные инициализация и уничтожение, для избежания которых придумали конструкторы, деструкторы и т.д... Можно же вообще обойтись без класса, не так ли? Почему же тогда вас не смущает вообще наличие классов в языке?
А теперь посмотрим на часть кода, приведенного вами:
static singleton& get_instance()
{
static singleton instance;
return instance;
}
Это т.н. синглтон Майерса. Чтобы достучаться до объекта, придется вызвать get_instance, что приведет к созданию объекта при первом вызове. Особенность данной реализации еще в том, что начиная с C++11, инициализация этого синглтона является потокобезопасной, что гарантируется непосредственно языком и не нужно городить костыли, в которых могут быть еще и ошибки как, например, печально известный double checked locking
P.S. И не забывайте про операторы присваивания и т.д.