Сокращение повторения кода в C++ (или x-treme x-macros)
Я использую x-макросы для уменьшения количества повторений и дублирования кода при реализации интерфейса Lua для игры Bitfighter. Следующий код работает нормально:
// Fn name Valid param profiles Profile count
# define TELEPORTER_LUA_METHOD_TABLE \
TELEPORTER_LUA_METHOD_ITEM(addDest, ARRAYDEF({{ PT, END }}), 1 ) \
TELEPORTER_LUA_METHOD_ITEM(delDest, ARRAYDEF({{ INT, END }}), 1 ) \
TELEPORTER_LUA_METHOD_ITEM(clearDests, ARRAYDEF({{ END }}), 1 ) \
// BLOCK A Start
const luaL_reg Teleporter::luaMethods[] =
{
# define TELEPORTER_LUA_METHOD_ITEM(name, b, c) { #name, luaW_doMethod<Teleporter, &Teleporter::name > },
TELEPORTER_LUA_METHOD_TABLE
# undef TELEPORTER_LUA_METHOD_ITEM
{ NULL, NULL }
};
// BLOCK A End
/* Generates the following:
const luaL_reg Teleporter::luaMethods[] =
{
{ "addDest", luaW_doMethod<Teleporter, &Teleporter::addDest > }
{ "delDest", luaW_doMethod<Teleporter, &Teleporter::delDest > }
{ "clearDests", luaW_doMethod<Teleporter, &Teleporter::clearDests > }
{ NULL, NULL }
};
*/
// BLOCK B Start
const LuaFunctionProfile Teleporter::functionArgs[] =
{
# define TELEPORTER_LUA_METHOD_ITEM(name, profiles, profileCount) { #name, profiles, profileCount },
TELEPORTER_LUA_METHOD_TABLE
# undef TELEPORTER_LUA_METHOD_ITEM
{ NULL, { }, 0 }
};
// BLOCK B End
/* Generates the following:
const LuaFunctionProfile Teleporter::functionArgs[] =
{
{ "addDest", {{ PT, END }}, 1 }
{ "delDest", {{ INT, END }}, 1 }
{ "clearDests", {{ END }}, 1 }
{ NULL, { }, 0 }
};
*/
#undef TELEPORTER_LUA_METHOD_TABLE
Все идет нормально.
За исключением того, что я делаю по сути то же самое в десятках классов. То, что я ДЕЙСТВИТЕЛЬНО хотел бы сделать, это определить таблицу методов в каждом классе (которую можно вызывать как угодно), а затем определить два макроса, которые можно вызывать так:
GENERATE_LUA_METHODS(Teleporter, TELEPORTER_LUA_METHOD_TABLE)
GENERATE_FUNCTION_PROFILE(Teleporter, TELEPORTER_LUA_METHOD_TABLE)
чтобы избежать всего повторения кода выше в блоках A и B. Очевидным способом является использование вложенного макроса, но это, к сожалению, недопустимо.
Есть ли способ лучше?
РЕШЕНИЕ
Когда я отправил этот вопрос, я был почти уверен, что ответ будет "не может быть сделано". Вместо этого я получил два подхода, один из которых был именно тем, что я искал. Также было хорошее обсуждение ловушек макросов (их много) с некоторыми альтернативными подходами. Реализация, которую я разработал на основе принятого ответа, проста и понятна, а грязные макрокоманды удобно скрыты от глаз.
Где-то в укрытии
#define ARRAYDEF(...) __VA_ARGS__ // Don't confuse the preprocessor with array defs
////////////////////////////////////////
////////////////////////////////////////
//
// Some ugly macro defs that will make our Lua classes sleek and beautiful
//
////////////////////////////////////////
////////////////////////////////////////
//
// See discussion of this code here:
// http://stackru.com/questions/11413663/reducing-code-repetition-in-c
//
// Start with a definition like the following:
// #define LUA_METHODS(CLASS, METHOD) \
// METHOD(CLASS, addDest, ARRAYDEF({{ PT, END }}), 1 ) \
// METHOD(CLASS, delDest, ARRAYDEF({{ INT, END }}), 1 ) \
// METHOD(CLASS, clearDests, ARRAYDEF({{ END }}), 1 ) \
//
#define LUA_METHOD_ITEM(class_, name, b, c) \
{ #name, luaW_doMethod<class_, &class_::name > },
#define GENERATE_LUA_METHODS_TABLE(class_, table_) \
const luaL_reg class_::luaMethods[] = \
{ \
table_(class_, LUA_METHOD_ITEM) \
{ NULL, NULL } \
}
// Generates something like the following:
// const luaL_reg Teleporter::luaMethods[] =
// {
// { "addDest", luaW_doMethod<Teleporter, &Teleporter::addDest > }
// { "delDest", luaW_doMethod<Teleporter, &Teleporter::delDest > }
// { "clearDests", luaW_doMethod<Teleporter, &Teleporter::clearDests > }
// { NULL, NULL }
// };
////////////////////////////////////////
#define LUA_FUNARGS_ITEM(class_, name, profiles, profileCount) \
{ #name, profiles, profileCount },
#define GENERATE_LUA_FUNARGS_TABLE(class_, table_) \
const LuaFunctionProfile class_::functionArgs[] = \
{ \
table_(class_, LUA_FUNARGS_ITEM) \
{ NULL, { }, 0 } \
}
// Generates something like the following:
// const LuaFunctionProfile Teleporter::functionArgs[] =
// {
// { "addDest", {{ PT, END }}, 1 }
// { "delDest", {{ INT, END }}, 1 }
// { "clearDests", {{ END }}, 1 }
// { NULL, { }, 0 }
// };
////////////////////////////////////////
////////////////////////////////////////
В каждом файле класса:
// Fn name Param profiles Profile count
#define LUA_METHODS(CLASS, METHOD) \
METHOD(CLASS, addDest, ARRAYDEF({{ PT, END }}), 1 ) \
METHOD(CLASS, delDest, ARRAYDEF({{ INT, END }}), 1 ) \
METHOD(CLASS, clearDests, ARRAYDEF({{ END }}), 1 ) \
GENERATE_LUA_METHODS_TABLE(Teleporter, LUA_METHODS);
GENERATE_LUA_FUNARGS_TABLE(Teleporter, LUA_METHODS);
#undef LUA_METHODS
2 ответа
Функциональный подход может решить многие ваши проблемы, но вы должны знать, что интенсивное использование препроцессора приводит к коду, который трудно отладить. вы обязаны тратить много времени на форматирование кода всякий раз, когда в сгенерированном коде есть синтаксическая ошибка (и вы обязательно столкнетесь с этим, когда использование вашего макроса значительно возрастет); это также повлияет на ваше настроение, когда вам нужно использовать GDB или что-то подобное.
следующее, очевидно, просто набросок, чтобы дать вам идею.
# define TELEPORTER_LUA_METHOD_TABLE(class_, item) \
item(class_, addDest, ARRAYDEF({{ PT, END }}), 1 ) \
item(class_, delDest, ARRAYDEF({{ INT, END }}), 1 ) \
item(class_, clearDests, ARRAYDEF({{ END }}), 1 ) \
# define LUA_METHOD_ITEM(class_, name, b, c) \
{ #name, luaW_doMethod<class_, &class_::name > },
# define LUA_FUNARGS_ITEM(class_, name, profiles, profileCount) \
{ #name, profiles, profileCount },
#define LUA_METHODS_TABLE(class_, table) \
const luaL_reg class_::luaMethods[] = \
{ \
table(class_, LUA_METHOD_ITEM) \
{ NULL, NULL } \
};
#define LUA_FUNARGS_TABLE(class_, table) \
const LuaFunctionProfile class_::functionArgs[] = \
{ \
table(class_, LUA_FUNARGS_ITEM) \
{ NULL, { }, 0 } \
};
LUA_METHODS_TABLE(Teleporter, TELEPORTER_LUA_METHOD_TABLE)
LUA_FUNARGS_TABLE(Teleporter, TELEPORTER_LUA_METHOD_TABLE)
#undef TELEPORTER_LUA_METHOD_TABLE
отредактируйте, чтобы ответить на вопрос Ватусимото из комментариев.
Ватусимото предлагает что-то вроде этого:
#define LUA_METHODS_TABLE(class_) \
const luaL_reg class_::luaMethods[] = \
{ \
LUA_METHOD_TABLE(class_, LUA_METHOD_ITEM) \
{ NULL, NULL } \
};
#define LUA_FUNARGS_TABLE(class_, table) \
const LuaFunctionProfile class_::functionArgs[] = \
{ \
LUA_METHOD_TABLE(class_, LUA_FUNARGS_ITEM) \
{ NULL, { }, 0 } \
};
#ifdef LUA_METHOD_TABLE
# undef LUA_METHOD_TABLE
#endif
# define LUA_METHOD_TABLE(class_, item) \
... class-specific definition ...
LUA_METHODS_TABLE(Teleporter)
LUA_FUNARGS_TABLE(Teleporter)
Недостатком этого является то, что неясно, как LUA_METHOD_TABLE связан с двумя последующими вызовами макросов. Это как если бы sprintf(3) не принимал аргументы и вместо этого ожидал данные в глобальных переменных определенных имен. С точки зрения понятности, любой кусок кода должен быть явным о своих непосредственных входных данных, вещах, над которыми он работает, и различающихся между его использованием. Но макрос глобальной таблицы также проигрывает в области компоновки: глобальный макрос исключает создание нескольких определений классов за один раз, например. с BPP или подобным.
Это довольно экстремальный взлом препроцессора, но вы можете сделать это с помощью нескольких разных файлов.
teleporter.cpp:
#define LMT_CLASS_NAME Teleporter
#define LMT_TABLE_FILE "Teleporter.lmt"
#include "lua_method_table.h"
lua_method_table.h:
#define LMT_METHOD_ITEM(name, b, c) { #name, luaW_doMethod<LMT_CLASS_NAME, &LMT_CLASS_NAME::name > },
const luaL_reg LMT_CLASS_NAME::luaMethods[] =
#include LMT_TABLE_FILE
{ NULL, NULL }
};
#undef LMT_METHOD_ITEM
#define LMT_METHOD_ITEM(name, profiles, profileCount) { #name, profiles, profileCount },
const LuaFunctionProfile LMT_CLASS_NAME::functionArgs[] =
{
#include LMT_TABLE_FILE
{ NULL, { }, 0 }
};
#undef LMT_METHOD_ITEM
И наконец teleporter.lmt:
LMT_METHOD_ITEM(addDest, ARRAYDEF({{ PT, END }}), 1 )
LMT_METHOD_ITEM(delDest, ARRAYDEF({{ INT, END }}), 1 )
LMT_METHOD_ITEM(clearDests, ARRAYDEF({{ END }}), 1 )
Вместо использования макроса для определения таблицы методов, она указана в файле, teleporter.lmt
, который включен дважды с разными определениями LMT_METHOD_ITEM
, Там нет защиты заголовка, поэтому она может быть включена столько раз, сколько необходимо. Если хочешь, можешь разделить lua_method_table.h
в два файла для обработки двух частей по отдельности. Просто включите оба из ваших файлов CPP.