【问题标题】:Enum vs Macro States C++枚举与宏状态 C++
【发布时间】:2010-07-13 19:27:53
【问题描述】:

(问题与我之前的问题herehereherehere有关)。

我正在维护一个多年前从 DOS 移植到 Windows 的非常古老的应用程序,但许多旧的 C 约定仍然沿用。

一个特殊的约定是 setBit 和 clrBit 宏:

#ifndef setBit
#define setBit(word, mask) word |= mask
#endif

#ifndef clrBit
#define clrBit(word, mask) word &= ~mask
#endif

我发现我可以将变量声明为枚举类型并将我的变量设置为等于定义的枚举值之一。

enum SystemStatus
{
    SYSTEM_ONLINE                = BIT0,
    SYSTEM_STATUS2               = BIT1,
    SYSTEM_STATUS3               = BIT2,
    SYSTEM_STATUS4               = BIT3
};

BIT0 = 0x00000001BIT1 = 0x00000002

SystemStatus systemStatus;

systemStatus = SYSTEM_ONLINE

在您看来,使用 setBit 和 clrBit 宏更像 C 还是 C++ - 将变量简单地声明为枚举类型并摆脱所有旧的 setBit/clrBit 东西会更好吗?

【问题讨论】:

  • 正如尼尔指出的那样,它们并不完全相同。也就是说,在 C++ 中,您永远不应该将宏用作函数,而应使用模板函数。此外,对于一组位,只需使用 std::bitset 并完成它。 (更好的界面。)
  • 为了以这种方式枚举所有可能的状态,您需要 2^(位数)状态。可能不是最干净的解决方案。
  • @Justin 这就是比特的本质。
  • Changeling 似乎在描述的是一个“单热”状态机,其中每个位代表一个状态,并且任何时候只允许设置一个位(所有其他组合都是非法的,并且会被switch 块中的default 子句捕获)。然而,这种符号在固件之外很少见,因为它的主要目标是通过添加寄存器来降低组合逻辑的复杂性,并且在纯软件状态机中并没有真正的区别。

标签: c++ macros enums


【解决方案1】:

不,你不能 - 分配枚举值会覆盖整个值,而宏正在更改现有值中的位。什么是 BIT0、BIT1 等?这就像定义 INT0、INT1 等 - 糟糕的做法。

归根结底,旧的 C 风格代码会给您带来什么问题吗?如果没有,请应用这条历史悠久的规则 - 如果它没有损坏,请不要修复它。

【讨论】:

  • 除非它们是零、一、二等位的常量。这还不错。 IMO 比 0x00004000 更容易阅读。
  • @James 你的意思是 BIT0 实际上可能被定义为 (1
  • @Neil:嗯,我更想从最低位开始计数,但我想任何一种方式都可以。
  • @James 无论哪种方式我都认为意味着不同架构的条件编译,在这种情况下,我宁愿将整个枚举包装在一个 #ifdef 块中,并在每个块中使用整数文字常量。但也许 OP 可以告诉我们 BIT0 等实际是如何定义的?
  • 代替BIT0BIT1等,通常的做法(至少在嵌入式系统的C中)是使用宏:#define BIT(n) (1 << (n))。然后另一组#define 宏会将位值分配给字段名称,例如#define SPI_START BIT(0),其中这些字段名称尽可能接近芯片数据表中的名称。此外,setBit 最好命名为setBits,因为您可以一次设置多个位(例如setBits(spiCtrlReg, SPI_START | SPI_CONT);
【解决方案2】:

setBit 和 clrBit 很好,尽管我会将它们转换为 C++ 中的内联函数。如果状态位彼此独立,它们将非常方便,例如:

  SystemStatus systemStatus = SYSTEM_ONLINE | SYSTEM_STATUS3;

是一个有效的设置。

systemStatus = clrBit(systemStatus, SYSTEM_STATUS3);
systemStatus = setBit(systemStatus, SYSTEM_STATUS4);

【讨论】:

  • +1:确实,像这样设置标志是常见且可接受的 C++。例如,只需查看iostream 标志。
【解决方案3】:

我认为您混淆了目的。枚举是关于设置值以用作标志。 setBit 和 clrBit 是关于对数据进行操作的。该数据可能恰好是一个标志,但这确实是这两个想法之间的唯一关系。

话虽如此,宏肯定不是 C++ 的做事方式。您将改用内联函数。例如:

template<typename T>
inline T& setBit(T& word, T mask) { return word |= mask; }

template<typename T>
inline T& clrBit(T& word, T mask) { return word &= ~mask; }

编辑以抵御挑剔者:

这只是您如何实现这些功能的一个示例。您不需要使用模板,您可以使用两个模板参数而不是 1,如果需要,您可以使用 void 函数或值而不是引用,(尽管这样会失去一些原始语义)。要点是获得在宏中找不到的类型安全的好处(在宏的许多其他缺点中)。 http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.5

编辑:这是一个用于比较的无效、非模板版本

inline void setBit(unsigned int word, unsigned int mask) { word |= mask; }

inline void clrBit(unsigned int word, unsigned int mask) { word &= ~mask; }

【讨论】:

  • 因此我评论的最后两句话;)。您需要将 SystemState 参数强制转换为 unsigned int,或者您可以使用两种类型的版本。我更喜欢前者,但主要是因为我非常尊重不进行任何自动转换的语言。
  • @Johannes 有很多事情需要投票。
  • @Cogwheel 鉴于 op != 和 op &= 可以为任何类型重载,您有可能在与位无关的类型上调用 setBit 和 clrBit。我不认为这是一个好主意。事实上,当涉及到 C++ 中的位操作时,对被操作的类型非常具体通常是一个好主意,甚至可能会说有必要这样做..
  • @Neil:如果我指定类型,那不是比使用没有类型安全性的宏更具体吗?
  • @Changeling 指定类型很好 - 普通函数比模板更安全。
【解决方案4】:

如果您确定在该代码的所有组合和排列中,人们一次只使用一个位,他们在设置之前清除并且从不连续设置两次,那么当然,使用而是枚举。它将更清晰,更具可读性。但是,如果有时系统是 0101,那么您的枚举无法处理。

好的,如果枚举是位标志,那么你可以写

systemStatus = SYSTEM_ONLINE | BIT2;

那我猜那是可读的,可以支持组合,ok。

【讨论】:

    【解决方案5】:

    使用enum,只有当你能确保一次设置的位不超过一个时才有效。否则,您必须为所有位组合提供一个枚举常量,这很快就会变得相当复杂。您可以使用一组#define 语句(或enum,我想)使用友好名称为位掩码设置别名,但您最终仍可能使用设置/清除宏。

    设置和清除位似乎更像是一种“类 C”的方法。但是,我(个人)不认为您的 enum 方法非常“类似于 C++”。对于更类似于 C++ 的方法,创建一个类来表示系统状态并操作类成员而不是位字段。您甚至可以重载 +- 运算符,使其分别像您的 setBitclrBit 函数一样。例如,使用systemStatus -= SYSTEM_ONLINE(与#define SYSTEM_ONLINE (1&lt;&lt;31) 或类似)清除“系统在线”位当且仅当它被设置时。您甚至可以从 STL Bitset 继承并重用大部分功能。

    编辑: OP 澄清 BIT0 等是位掩码,所以我的第一段不再相关。

    【讨论】:

      【解决方案6】:

      我同意 bta 的观点,如果你想使用 C++ 方法,你应该创建一个抽象所有关于状态的实现的类。

      但我不会重载 +=、-= 运算符,因为您继续使用 C 老派。

      我建议声明方法来做到这一点。

      您可以选择一种带有布尔标志的方法或两种用于设置和清除的方法。

      class Status{...};
      
      void main(){
          Status status;
      
          //first approach
          status.SystemOnline(true);
          status.BackUPOnline(false);
      
          //second approach
          status.SetEmergencySystem();
          status.ClearSystemOnline();
      }
      

      使用这种样式,您可以封装有关如何实现信息存储的实现。

      【讨论】:

        猜你喜欢
        • 2015-04-26
        • 2023-03-27
        • 2014-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多