5

ゲーム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

ここまでは順調ですね。

私が何十ものクラスで本質的に同じことをすることを除いて。私が本当にやりたいことは、各クラスでメソッドテーブルを定義し(これは何でも呼び出すことができます)、次に次のように呼び出すことができる2つのマクロを定義することです:

GENERATE_LUA_METHODS(Teleporter, TELEPORTER_LUA_METHOD_TABLE)
GENERATE_FUNCTION_PROFILE(Teleporter, TELEPORTER_LUA_METHOD_TABLE)

上記のブロック A と B で繰り返されるコードをすべて回避するためです。明らかな方法は、ネストされたマクロを使用することですが、残念ながら、これは違法です。

より良い方法はありますか?


解決


この質問を投稿したとき、答えは「できない」と確信していました。代わりに、2 つのアプローチがありましたが、そのうちの 1 つはまさに私が探していたものでした。また、マクロの落とし穴 (多数あります) についても良い議論があり、いくつかの代替アプローチが提案されました。受け入れられた回答に基づいて私が開発した実装は、クリーンで理解しやすく、汚れたマクロが見えない便利な場所にあります。

どこかの隠れ家で:

#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
4

2 に答える 2

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

編集して、コメントから 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)

これの欠点は、続く 2 つのマクロ呼び出しに LUA_METHOD_TABLE がどのように関連しているかが明確でないことです。あたかも sprintf(3) が引数を取らず、代わりに特定の名前のグローバル変数のデータを期待しているかのようです。分かりやすさの観点からは、コードのどの部分も、直接の入力、動作するもの、用途によって異なるものについて明示する方がよいでしょう。しかし、グローバル テーブル マクロは、コンポーザビリティの面でも負けています。グローバル マクロは、一度に複数のクラス定義を生成することを妨げます。BPPまたは類似のものを使用します。

于 2012-07-10T14:09:56.670 に答える
1

これはかなり極端なプリプロセッサ ハッカーですが、いくつかの異なるファイルでこれを行うことができます。

テレポーター.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、 の異なる定義で 2 回含まれていますLMT_METHOD_ITEM。そこにはヘッダー ガードがないため、必要な回数だけ含めることができます。必要に応じて、lua_method_table.h2 つのファイルに分割して、2 つの部分を別々に処理することもできます。CPP ファイルから両方を含めるだけです。

于 2012-07-10T13:31:05.597 に答える