Я хочу определить макрос, который можно вызвать в разных местах (в области файлов), чтобы создавать функции, которые что-то делают. (В приведенном ниже примере функции просто печатают сообщение, но, конечно, мое реальное намерение состоит в том, чтобы сделать некоторые другие полезные вещи.) Проблема в том, что мне нужна некоторая "менеджерская" функция (в моем примере это будет просто main()
) каким-то образом добиться того, чтобы все они были вызваны (в любом порядке) без какого-либо кода, зависящего от макросов-вызовов (за исключением самих макросов-вызовов, конечно). Я имею в виду, что после того, как файл написан, другой программист сможет просто вставить несколько новых макросов в разных местах или удалить некоторые из существующих вызовов, и код будет работать без каких-либо дальнейших изменений. Я понимаю, что это можно сделать с использованием статических объектов, но я хочу изучить другой подход. Я собираюсь использовать некоторые шаблонные трюки и тот факт, что __LINE__
является монотонным увеличением.
#include <iostream>
using namespace std;
template<int i>
inline void f()
{
f<i-1>();
}
#define START_REGISTRATION \
template<> \
inline void f<__LINE__>() {} /* stop the recursion */ \
template<> void f<__LINE__>() /* force semicolon */
#define REGISTER(msg) \
template<> \
inline void f<__LINE__>() \
{ \
cout << #msg << endl; \
f<__LINE__ - 1>(); \
} \
template<> void f<__LINE__>() /* force semicolon */
// Unrelated code ...
START_REGISTRATION;
// Unrelated code ...
REGISTER(message 1);
// Unrelated code ...
REGISTER(message 2);
// Unrelated code ...
REGISTER(message 3);
// Unrelated code ...
// manager function (in this case main() )
int main()
{
f<__LINE__>();
}
Отпечатает
message 3
message 2
message 1
как ожидалось.
Это решение имеет несколько недостатков.
- Невозможно дважды вызвать
REGISTER
в той же строке. - Разрыв, если
#line
воспроизводится с. - Нужно поставить менеджера после всех вызовов
REGISTER
. - Увеличено время компиляции из-за рекурсивных экземпляров.
- Если все "dummy" экземпляры
f
все отложены, глубина стека вызовов во время выполнения будет равна количеству строк междуSTART_REGISTRATION;
иf<__LINE__>();
в менеджере. - Наложение кода: если только "dummy" экземпляры
f
не вставлены, количество экземпляров будет таким же большим. - Чрезмерная глубина рекурсивности инстанцирования, вероятно, попадет в предел компилятора (по умолчанию 500 в моей системе).
Вопросы 1-4 Я не против. Проблема 5 может быть устранена, если каждая функция возвращает указатель на предыдущую, и если менеджер использует эти указатели для вызова функций итеративно, а не для вызова друг друга. Выпуск 6 можно устранить, создав аналогичную конструкцию шаблона класса, которая может вычислять для каждого вызова REGISTER
, какая функция была создана в предыдущем вызове и, таким образом, только создала экземпляр функций, которые действительно что-то делают. Затем избыточное инстанцирование будет смещено от шаблона функции к шаблону класса, но экземпляры класса будут облагать только компилятором; они не будут вызывать генерации кода. Итак, моя настоящая проблема - проблема 7, и возникает вопрос: существует ли способ реструктурировать такие вещи, чтобы компилятор делал экземпляры итеративно, а не рекурсивно. Я также открыт для разных подходов (кроме тех, которые связаны с статическими объектами). Одним простым решением является объединение всех регистраций вместе непосредственно перед менеджером (или добавление макроса STOP_REGISTRATION
для завершения блокировки регистраций), но это может привести к нарушению значительной части моей цели (регистрации материала рядом с кодом, который его определяет).
Изменить: Были некоторые интересные предложения, но я боюсь, что не понимаю, что я надеюсь достичь. Меня действительно интересуют две вещи: решение поставленной задачи (т.е. Не статика, одна строка для регистрации, никаких дополнительных изменений при добавлении/удалении регистраций, и, хотя я пренебрег этим сказать, только стандартный С++ --- следовательно, без повышения). Как я уже сказал в комментариях ниже, мой интерес более теоретический по своей природе: я надеюсь изучить некоторые новые методы. Следовательно, я бы очень хотел сосредоточиться на реструктуризации, поэтому рекурсия устраняется (или, по крайней мере, сокращается) или находит другой подход, который удовлетворяет ограничениям, изложенным выше.
Изменить 2: Решение MSalter - большой шаг вперед. Сначала я думал, что каждая регистрация приведет к полной стоимости строк до него, но потом я понял, что, конечно, функция может быть создана только один раз, поэтому с точки зрения экземпляров мы платим ту же цену, что и в линейном поиске, но глубина рекурсии становится логарифмической. Если я подойду к нему, я опубликую полное решение, устраняющее проблемы 5-7. Тем не менее, было бы неплохо увидеть, можно ли это сделать с постоянной глубиной рекурсии и, что лучше, с количеством экземпляров, линейным по количеству вызовов (a-la для ускорения).
Изменить 3: Здесь полное решение.
#define START_REGISTRATION \
template<int lo, int hi> \
struct LastReg { \
enum { \
LINE_NUM = LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM ? \
static_cast<int>(LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM) : \
static_cast<int>(LastReg<lo, (lo + hi)/2>::LINE_NUM) \
}; \
}; \
template<int l> \
struct LastReg<l, l> { \
enum { LINE_NUM = 0 }; \
}; \
template<int l> \
struct PrevReg { \
enum { LINE_NUM = LastReg<__LINE__ + 1, l - 1>::LINE_NUM }; \
}; \
template<int l> void Register() {} \
template<int l> void Register() /* force semicolon */
#define REGISTER(msg) \
template<> \
struct LastReg<__LINE__, __LINE__> { \
enum { LINE_NUM = __LINE__ }; \
}; \
template<> \
void Register<__LINE__>() \
{ \
cout << __LINE__ << ":" << #msg << endl; \
Register<PrevReg<__LINE__>::LINE_NUM>(); \
} \
template<> void Register<__LINE__>() /* force semicolon */
#define END_REGISTRATION \
void RegisterAll() \
{ \
Register<PrevReg<__LINE__>::LINE_NUM>(); \
} \
void RegisterAll() /* force semicolon */
START_REGISTRATION;
REGISTER(message 1);
REGISTER(message 2);
END_REGISTRATION;
int main()
{
RegisterAll();
}