Бывает, что в программе нужно обеспечить наличие единственного доступного отовсюду экземпляра какого-нибудь класса. При этом, разумеется, таскать повсюду указатель на него нам совершенно не хочется и боже упаси от глобальных переменных! Энциклопедии при этом советуют брать шаблон Singleton и воплощать его без страха и упрека в полном соответствием с курсом партии и правительства.
Мы, как всегда, пойдем другим путем. На самом деле, во многих случаях городить Singleton совершенно не нужно, так как можно было бы просто обойтись глобальной статической переменной где-нибудь в коде и обращаться к ней по мере необходимости. Такое бывает нужно отнюдь нередко. Загвоздка в том, что мы не хотим таскать с собой глобальную переменную, но хотелось бы быть уверенным, что экземпляр класса существует (был создан) на момент любого к нему обращения и что это один и тот же экземпляр.
Тогда просуммируем все наши хотелки примерно таким псевдонаучным выражением “необходим механизм, производящий автоматическую инициализацию единственного объекта определенного типа до первого обращения к нему и обеспечивающий доступ к данному конкретному экземпляру из любого места программы. Выявить ограничения механизма.”.
Выделенное слово “тип” сразу наводит на мысль о шаблонах. Шаблоны – это отличный способ генерировать столько разных типов данных, сколько нам потребуется. Например, vector<char> и vector<unsigned char> – два класса, хоть и идентичных с виду, но абсолютно разного типа. Вроде как если бы у нас были classA и classB.
Теперь вспоминаем слово static и бросаемся в бой
template <class T>
class tStaticInstance
{
public:
static T* GetInstance();
private:
static T m_Instance;
}
template <class T>
T tStaticInstance<T>::m_Instance = T();
Это действительно все, что нужно, чтобы иметь глобальный статический объект класса T без необходимости явно объявлять глобальную статическую переменную в каком-нибудь заголовочном файле и потом повсюду носиться с ним. Вместо этого получаем:
#include "tStaticInstance.h"/// somewhere somewhen tStaticInstance<MyConcreteType>::GetInstance()->SomeMethod(); tStaticInstance<OtherType>::GetInstance()->SomeMethod();
Код выглядит страшновато на первый взгляд, но на самом деле убивает сразу несколько зайцев:
- Нет необходимости модифицировать MyConcreteType и OtherType. Главное – чтобы в них был конструктор по умолчанию
- Можно создать статическую переменную любого типа из любого места программы без необходимости ее явного объявления
- Нет присущих singleton’у проблем
- Из выражения явно видно, что нам необходим единственный экземпляр этого класса. В следующих статьях я приведу пример, когда это может быть полезно.
- Код прост как дважды два
- Не нужно следить за глобальными статическими объектами - если после очередной сессии рефакторинга какой-то статический объект оказался не у дел, то он больше создаваться не будет.
Естественно, такой шаблон имеет свои ограничения:
- Уникальность экземпляра гарантируется только в пределах одного бинарника. Впрочем, организовывать синглтон, гарантирующий единственность экземпляра как в главном модуле, так и во всех dll, до сих пор берутся только сильные духом
- Экземпляр инстанцируется всегда, даже если во время работы программы ни разу использован не будет. Тут классический singleton, безусловно, выигрывает.
Еще нет защиты от дурака, например
MyType pInstance* = tStaticInstance<MyConcreteType>::GetInstance(); delete pInstance; // Something bad will happen
Но знаете ли, в таких вещах главное – это соблюсти баланс, иначе код получится настолько идиотозащищенным, что только идиоты и захотят им пользоваться.
Хм, мне кажется, что уникальность-то не гарантируется как раз. То есть, все от линкера зависит, удалит он дубликаты правильно или нет.
Возможно. 14.7.2/7 говорит, что
Это гарантирует уникальность в пределах одного translation unit. На практике же архаичных линкеров уже не встречается, КМК.
А что за “присущие синглтону проблемы” имеются в виду, позвольте спросить?
И кстати, для сильных духом любителей шаблонной магии… Зачем заниматься байсиклинвеншеном, если есть Loki<library>?
Достаточно их. Один только потенциальный геморрой внутри многопоточной аппликухи при инициализации чего стоит.
Локи – это не мое. Мы балуемся чем потяжелее, бустом там.