【问题标题】:Reducing code repetition in C++ (or x-treme x-macros)减少 C++(或 x-treme x-macros)中的代码重复
【发布时间】: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


【解决方案1】:

函数式方法可以解决您的许多问题,但您应该意识到,大量使用预处理器会导致代码难以调试。每当生成的代码中出现语法错误时,您一定会花费大量时间格式化代码(并且当您的宏使用充分增长时,您一定会遇到这种情况);当你需要使用 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

编辑回答来自 cmets 的 Watusimoto 的问题。

Watusimoto 提出这样的建议:

#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 或类似的。

【讨论】:

  • 第一次阅读时,这看起来非常好。通过规范表名,我们可以去掉最后两个宏中的第二个参数,留下:LUA_METHODS_TABLE(Teleporter); LUA_FUNARGS_TABLE(Teleporter);,确实很不错。
  • @Watusimoto:如果您打算使用预处理器,我建议您查看 Boost.Preprocessor。他们不仅有 LOTS 用于迭代和填充的宏,而且还展示了可以完成的工作以及如何
  • @Watusimoto:我不会那样做。您必须在每次使用前#undef 表;参考透明度很好。
  • 我不确定我是否遵循 - 在您上面发布的代码中,您在使用后#undef 表。这还不够吗?
  • @Watusimoto:我已经更新了答案,如果我歪曲了你的立场,请编辑它。
【解决方案2】:

这是一些相当极端的预处理器骇客,但您可以使用几个不同的文件来做到这一点。

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 文件中即可。

【讨论】:

  • 恐怕这可能是一个需要极端预处理器骇客的问题:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-05
  • 2019-09-30
相关资源
最近更新 更多