【发布时间】:2012-07-10 12:48:37
【问题描述】:
在为游戏Bitfighter 实现 Lua 接口时,我正在使用 x-macros 来减少重复次数和代码重复。以下代码工作正常:
// 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://stackoverflow.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
【问题讨论】:
-
我非常赞成使用脚本生成大量样板代码。无论如何,您都在有效地创建自己的 DSL,使用的宏可能会令人不快地调试和理解。 C 宏不是特别具有表达力,编写代码生成器可能更容易,生成的代码可能更具可读性和可调试性。
-
我同意您评论的主旨,并同意您对 C 宏的厌恶。但是,这必须在各种不同的平台上编译,并且使用外部工具生成代码可能会使构建过程过于繁琐。
-
我使用过(或编写过)代码生成器,从简单的 awk 脚本到 python,再到必须首先编译的 C 程序。他们从来没有让构建过程过于繁琐 - 你编写规则一次,然后运行
make(或其他)。 -
@Watusimoto:将(比如说)一个 perl 脚本集成到构建系统中远不如必须制作自己的复杂宏系统那么令人不快。我已经在 Visual Studio、Maven 和为各种项目中完成了这一点。如果你觉得特别受虐,你可以用 C++ 编写代码生成器,这样你只需要管理一种语言,而且它仍然比你放在一起的那种宏怪物更易于管理。
-
我们目前支持 gcc (with make)、VC++ 和 XCode。我们的项目目标之一是减少人们使用代码的障碍,因此构建所需的工具越少越好。也就是说,代码还需要易于理解,因此需要进行明确的权衡。目前,我预计事情不会比我在这里介绍的复杂得多,因此内置预处理器似乎是最不坏的选择。如果事情变得更复杂,那么计算可能会发生变化,我们可能希望支持更精细(且易于理解)的预处理系统。
标签: c++ c-preprocessor x-macros