【问题标题】:Is my macro a little bit too naughty? [closed]我的宏是不是有点太调皮了? [关闭]
【发布时间】:2018-02-08 16:14:43
【问题描述】:

我做了一个宏。我担心它可能有点“太顽皮”,因为宏是邪恶的

这是没有宏的代码:

case SDLK_a:
    _buffer_.InsertAtCursor('a');
    _buffer_.MutableCursor().Right();
    break;

这里是宏定义及其用法:

#define SDLK_KEY_CASE_MACRO(X, Y) \
{\
case X: \
    _buffer_.InsertAtCursor(Y); \
    _buffer_.MutableCursor().Right(); \
    break;\
}

SDLK_KEY_CASE_MACRO(SDLK_b, 'b');
SDLK_KEY_CASE_MACRO(SDLK_c, 'c');
SDLK_KEY_CASE_MACRO(SDLK_d, 'd');
SDLK_KEY_CASE_MACRO(SDLK_e, 'e');
SDLK_KEY_CASE_MACRO(SDLK_f, 'f');
SDLK_KEY_CASE_MACRO(SDLK_g, 'g');
SDLK_KEY_CASE_MACRO(SDLK_h, 'h');
SDLK_KEY_CASE_MACRO(SDLK_i, 'i');
SDLK_KEY_CASE_MACRO(SDLK_j, 'j');
SDLK_KEY_CASE_MACRO(SDLK_k, 'k');
SDLK_KEY_CASE_MACRO(SDLK_l, 'l');
SDLK_KEY_CASE_MACRO(SDLK_m, 'm');
SDLK_KEY_CASE_MACRO(SDLK_n, 'n');
SDLK_KEY_CASE_MACRO(SDLK_o, 'o');
SDLK_KEY_CASE_MACRO(SDLK_p, 'p');
SDLK_KEY_CASE_MACRO(SDLK_q, 'q');
SDLK_KEY_CASE_MACRO(SDLK_r, 'r');
SDLK_KEY_CASE_MACRO(SDLK_s, 's');
SDLK_KEY_CASE_MACRO(SDLK_t, 't');
SDLK_KEY_CASE_MACRO(SDLK_u, 'u');
SDLK_KEY_CASE_MACRO(SDLK_v, 'v');
SDLK_KEY_CASE_MACRO(SDLK_w, 'w');
SDLK_KEY_CASE_MACRO(SDLK_x, 'x');
SDLK_KEY_CASE_MACRO(SDLK_y, 'y');
SDLK_KEY_CASE_MACRO(SDLK_z, 'z');

显然,这为我节省了很多打字时间和很多代码行。代码也编译运行正常。

  • 但是这个宏是不是太调皮了? (它是否可能会产生意想不到的行为,或者我有什么理由不应该在我的代码中使用这种“作弊”?)

【问题讨论】:

  • 它们在 C++ 中比在 C 中更邪恶,所以你应该选择一个。
  • “很明显,这为我节省了大量的打字和代码行”——没错,但首先需要这些代码吗?
  • 宏就像你用来在墙上钻螺丝的锤子一样邪恶......
  • 你不能用_buffer_.InsertAtCursor(value - SDLK_b + 'b'); _buffer_.MutableCursor().Right();替换整个switch吗?说到_buffer_.MutableCursor().Right();部分肯定不需要在switch中。
  • 编写一个函数(或使用表格/地图)将SDLK_... 转换为... 并删除开关。如果这不能做到,我更喜欢“真正的”开关,所有的行都超过宏

标签: c++ macros


【解决方案1】:

像任何健全的系统一样,SDL_SCANCODE_A(又名SDLK_a)在一个连续的整数范围内,并且一直到SDL_SCANCODE_Z'a''z' 也是如此。

if( val >= SDLK_a && val <= SDLK_z ) {
  _buffer_.InsertAtCursor( 'a' + (val-SDLK_a) );
  _buffer_.MutableCursor().Right();
} else {
  // handle other cases
}

这比一堵宏墙更高效、更短、更清晰。

您可能需要重复数字。

对于特殊字符(制表符、引号等),我不会依赖精心安排的内容。在那里你会想要一张短桌

struct SDL_table {
  unsigned code; 
  char value;
};

SDL_table table[] = {
  {SDL_SCANCODE_TAB, '\t'},
  // ... etc
};

然后,在之前的检查失败后,快速搜索该表

auto it = std::find_if( std::begin(table), std::end(table), [&](SDL_table t) { return t.code == val; } );
if (it != std::end(table)) {
  _buffer_.InsertAtCursor( it->value );
  _buffer_.MutableCursor();
} else {
  // deal with unrecognized scancode
}

此时我可能会重构 _buffer_ 代码。

char SDLK_to_char( unsigned sdlk ) {
  if( sdlk >= SDLK_a && sdlk <= SDLK_z )
     return 'a' + (val-SDLK_a);
  if( sdlk >= SDLK_0 && sdlk <= SDLK_9 )
     return '0' + (sdlk-SDLK_0);
  struct SDLK_entry {
    unsigned code; 
    char value;
  };

  static const SDLK_entry table[] = {
    {SDL_SCANCODE_TAB, '\t'},
    // ... etc
  };

  auto it = std::find_if( std::begin(table), std::end(table),
   [&](auto&& t) { return t.code == sdlk; }
  );
  if (it != std::end(table))
    return it->value;
  return 0; // null for failure
}

然后

char c = SDLK_to_char( sdlk );

if (c) {
  _buffer_.InsertAtCursor( c );
  _buffer_.MutableCursor().Right();
}

【讨论】:

    【解决方案2】:

    好吧,我会避免使用宏 - 即使我必须用案例编写完整的 switch

    但首先我会考虑另一种方法。

    例如将SDLK_... 转换为... 的查找表。在 C++ 中,您可以基于 std::map

    喜欢:

      std::map<SDLK_type, char> look_up_table = {{SDLK_a, 'a'}, {SDLK_b, 'b'}};
    
      auto i = look_up_table.find(SDLK_value);
      if (i != look_up_table.end())
      {
        char c = i->second;
        _buffer_.InsertAtCursor(c);
        _buffer_.MutableCursor().Right();
      }
      else
      {
        std::cout << "Not found" << std::endl;
      }
    

    【讨论】:

    • 表格的初始化结束了......想要在里面有一个宏。
    • 表应该用初始化器来初始化,而不是函数。
    • std::map&lt;SDLK_TYPE, char&gt; 会降低复杂性。
    • @Jarod42 - 同意。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多