Основная идея достаточна прямолинейна — создаём статическую переменную, конструктор которой вызывает static_ctor(), а деструктор static_dtor(). Но дьявол, как говорится, в деталях. Как заставить компилятор инстанциировать статическую переменную шаблонного класса, так сказать, "изнутри" этого же класса? У меня на это ушло примерно 2 года непрерывных медитаций
Понятно, что надо как-то использовать эту статическую переменную (helper_), что бы заставить компилятор её инстанциировать. Со студией всё достаточно просто. Достаточно создать виртуальную функцию (use_helper2), которая упоминает статическую переменную. По стандарту будет ли инстанциироваться виртуальная функция — implementation defined. Но студия инстанциирует, поэтому всё замечательно.
Основная засада с gcc, т.к. он, сволочь, не инстанциирует виртуальные функции шаблонных классов без особой надобности. При наследовании от шаблонного класса гарантированно по стандарту инстанциируются только объявления всех членов. Поэтому надо как-то из объявления члена класса использовать статическую переменную. Решение следующее. Параметризуем вспомогательный шаблонный класс (helper2) адресом статической функции (use_helper). Поскольку от функции берётся адрес, gcc её инстанциирует (логично). Далее просто — функция упоминает статическую переменную (helper_).
В принципе тут можно пойти более коротким путём — параметризовать вспомогательный класс сразу адресом нужной статической переменной (helper_). Но я столкнулся с тем, что не все компиляторы хорошо это переваривают, хотя на некоторых gcc это работает как полагается.
В итоге это решение работает на msvc7.1, msvc8, msvc9, gcc3.4, gcc 4.1. На Comeau online — компилируется без ошибок, что радует.
Могу привести как я это использую в библиотеке асинхронного агентного программирования.
Пользователь описывает свой класс агента следующим образом:
class my_agent : public zzz::agent<my_agent>
{
// помимо вкусностей, которые нам даёт наследование и CRTP,
// у агента есть функция статической подписки на сообщения.
// Она вызывается из "конструктора базового класса" zzz::agent<>,
// который сам делает некую работу по регистрации данного типа агента,
// по инициализации необходимых структур данных,
// а потом вызывает пользовательскую функцию zzz_subscribe()static void zzz_subscribe()
{
subscribe<msg1>();
subscribe<msg2>();
subscribe<msg3>();
}
};
По-моему, получается очень красиво и удобно для пользователя. Никаких дополнительных макросов, никаких дополнительных вызовов функций, которые надо "не забыть" сделать. Вся инициализация гарантированно происходит "до main", т.o. в main я гарантированно знаю ВСЕ типы агентов (и аналогично для типов сообщений), которые есть в программе, это позволяет строить необходимые вспомогательные таблицы в виде массивов, не боясь и не предусматривая возможность динамического роста.
Ну или классический пример, если в программе есть много типов чего-либо — ну там типов запросов на сервере, или типов команд в клиентском приложении, тогда описывать их можно следующим образом:
class request_123 : public request<request_123>
{
// тут уже никаких дополнительных действий не надо,
// т.к. наш базовый класс request<> сам в своём "статическом конструкторе"
// зарегистрирует этот тип обработчика в фабрике обработчиков
};
Здравствуйте, remark
R>В итоге это решение работает на msvc7.1, msvc8, msvc9, gcc3.4, gcc 4.1. На Comeau online — компилируется без ошибок, что радует.
Здравствуйте, remark, Вы писали:
R>По-моему, логично. А как классу жить без вызванного конструктора? А если ты включишь заголовок с определением объекта в несколько длл, в каждой длл тоже будет свой объект...
R>
IMHO логично бы было, если бы инициализация вызывалась только в той dll которая экспортирует эту штуку...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Если я правильно понимаю ситуацию, то вся реализация лежит в заголовчном файле. Представим что этот заголовочник используется в статической библиотеке libMyLib.lib. В ней инстанцируется class_initializer<> для какого-нибудь типа Foo, объявленного и определённого в этой же библиотеке. А теперь главное не дать пользователю библиотеки самому инстанцировать class_initializer<Foo> и запретить ему включать заголовочник библиотеки, в котором объявлен class_initializer<Foo>. Иначе получим двойное опредление переменной class_initializer<Foo, Foo>::helper_.
Здравствуйте, Mazay, Вы писали:
M>Если я правильно понимаю ситуацию, то вся реализация лежит в заголовчном файле. Представим что этот заголовочник используется в статической библиотеке libMyLib.lib. В ней инстанцируется class_initializer<> для какого-нибудь типа Foo, объявленного и определённого в этой же библиотеке. А теперь главное не дать пользователю библиотеки самому инстанцировать class_initializer<Foo> и запретить ему включать заголовочник библиотеки, в котором объявлен class_initializer<Foo>. Иначе получим двойное опредление переменной class_initializer<Foo, Foo>::helper_.
Двойных определений статических переменных (так же как и функций) шаблонных классов не бывает. Они по-умолчанию работают так как будто они __declspec(selectany) (а фукнции так как будто они inline).
Здравствуйте, remark, Вы писали:
R>Двойных определений статических переменных (так же как и функций) шаблонных классов не бывает. Они по-умолчанию работают так как будто они __declspec(selectany) (а фукнции так как будто они inline).
R>
Вот если бы ещё и dll не было...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
приложении, тогда описывать их можно следующим образом: R>
R> // тут уже никаких дополнительных действий не надо,
R> // т.к. наш базовый класс request<> сам в своём "статическом конструкторе"
R> // зарегистрирует этот тип обработчика в фабрике обработчиков
R>
R>
Супер! Так бы сразу и писал, что это всё проолжение темы о построении списка деклараций в CT. В CT похоже облом, и пришлось отступить до этапа статических конструкторов. В целом прикольно. Единственный существенный недостаток для пользователя -- труно врубиться когда и кто зовёт твою функцию ну да RTFM!
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
R>>Двойных определений статических переменных (так же как и функций) шаблонных классов не бывает. Они по-умолчанию работают так как будто они __declspec(selectany) (а фукнции так как будто они inline).
E>Вот если бы ещё и dll не было...
Здравствуйте, remark, Вы писали:
R>Тут есть какие-то проблемы с dll'ями?
Если я заведу клиентсякий класс, то в каждой dll в которую будет включаться его хедер добавится вызоывалка инициализации...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
R>>Тут есть какие-то проблемы с dll'ями?
E>Если я заведу клиентсякий класс, то в каждой dll в которую будет включаться его хедер добавится вызоывалка инициализации...
По-моему, логично. А как классу жить без вызванного конструктора? А если ты включишь заголовок с определением объекта в несколько длл, в каждой длл тоже будет свой объект...
Забыл сказать, что я у себя определил пустые методы static_ctor()/static_dtor() в классе class_initializer<>, т.ч. если какой-то из них не нужен, что его можно просто не определять.
AG>
AG>struct D : S
AG>{
AG> int a(); OVERRIDE_CHECK(&D::a, &S::a);
AG> virtual int b(); OVERRIDE_CHECK(&D::b, &S::b);
AG>};
AG>
При желании можно OVERRIDE_CHECK помещать прямо в тело функции, что бы не загромождать h-ник:
Здравствуйте, remark, Вы писали:
R>Забыл сказать, что я у себя определил пустые методы static_ctor()/static_dtor() в классе class_initializer<>, т.ч. если какой-то из них не нужен, что его можно просто не определять.
Кажется, я неправильно понимаю SFINAE.
template<class D, class B>
struct check : class_initializer<check<D, B> >
{
static void static_ctor()
{
static_cast<D>(B());
}
};
int main()
{
check<void*, double> f; // почему мой static_ctor не выбрасывается и не вызывается унаследованный
}
R>При желании можно OVERRIDE_CHECK помещать прямо в тело функции, что бы не загромождать h-ник: R>
Или ещё куда-нибудь.
На самом деле мне хотелось поместить его именно там, т.к. хотелось съиммитировать override в MSVC.
Ещё хотелось довести все проверки до compile-time.
Однако в проверке d==static_cast<D>(b):
1. Равенство функций членов можно проверить только так template<D, D> struct eq; template<D d> struct eq<d, d> {};, т.е. они д.б. шаблонными параметрами одного типа.
2. неявного приведения шаблонных параметров нет, template<class D, class B, D d, B b, D b1 = b> — не катит.
3. static_cast нельзя использовать в CT выражениях (хотя в MSVC можно).
4. константа-статический-член типа указатель-на-член-метод не является CT-константой.
Ну и по второй проверке typeid нелья использовать в CT (хотя в MSVC можно).
Единственная проверка, которая дошла до СТ — проверка сигнатур. static_cast<D>(b) — при разных синатурах d и b так не получится.
Поэтому, используя class_initializer, делаем проверки в RT. Но строго один раз и до main
Я сам долго медитировал на эту тему, однако меня смущало, что в VC решение не работало, в то время как это работало в остальных компиляторах. Implementation defined решения использовать не хотелось. Мне удалось добиться одинакового поведения на всех компиляторах для частного случая