【问题标题】:How to overload |= operator on scoped enum?如何在作用域枚举上重载 |= 运算符?
【发布时间】:2013-03-31 03:26:59
【问题描述】:

如何在强类型(作用域)enum(在 C++11、GCC 中)上重载 |= 运算符?

我想测试、设置和清除强类型枚举的位。为什么要强类型?因为我的书说这是很好的做法。但这意味着我必须在任何地方static_cast<int>。为了防止这种情况,我重载了|& 运算符,但我不知道如何在枚举上重载|= 运算符。对于一个类,您只需输入 the operator definition in the class,但对于在语法上似乎不起作用的枚举。

这是我目前所拥有的:

enum class NumericType
{
    None                    = 0,

    PadWithZero             = 0x01,
    NegativeSign            = 0x02,
    PositiveSign            = 0x04,
    SpacePrefix             = 0x08
};

inline NumericType operator |(NumericType a, NumericType b)
{
    return static_cast<NumericType>(static_cast<int>(a) | static_cast<int>(b));
}

inline NumericType operator &(NumericType a, NumericType b)
{
    return static_cast<NumericType>(static_cast<int>(a) & static_cast<int>(b));
}

我这样做的原因:这就是它在强类型 C# 中的工作方式:一个枚举只有一个结构,其基础类型的字段,以及在其上定义的一堆常量。但它可以有任何适合枚举隐藏字段的整数值。

而且似乎 C++ 枚举的工作方式完全相同。在这两种语言中,强制转换都需要从 enum 到 int,反之亦然。但是,在 C# 中,位运算符默认是重载的,而在 C++ 中则不是。

【问题讨论】:

  • 我不确定这是否有意义。单个被枚举,但PadWithZero | NegativeSign = 0x03 不是一个有效的枚举常量。
  • @Useless 是的,这只是一个基于 Linux 0.1 中用于实现printf 的 NUMERICTYPE_ 定义序列的 C++11 版本的示例。结果必须是原始枚举的成员吗?我来自 C# 背景,期望作用域枚举的行为类似于 C# 中的枚举。
  • 我是说您的枚举类型在| 下没有关闭,因此将其强制为该类型的(非法值)是没有意义的。枚举标志常量值,但让标志组合为int。
  • @Useless - 该值非法。任何适合位的值都可以。那种位掩码一直都是做的,没毛病。
  • 我会说 NumericType 的组合是一个集合,它是与 NumericType 不同的类型。你为什么不创建一个 NumericTypeSet 类或其他东西?从语义上讲,这可能更有意义。

标签: c++ c++11 enums operator-overloading bitwise-operators


【解决方案1】:
inline NumericType& operator |=(NumericType& a, NumericType b)
{
    return a= a |b;
}

这行得通吗? Compile and run: (Ideone)

#include <iostream>
using namespace std;

enum class NumericType
{
    None                    = 0,

    PadWithZero             = 0x01,
    NegativeSign            = 0x02,
    PositiveSign            = 0x04,
    SpacePrefix             = 0x08
};

inline NumericType operator |(NumericType a, NumericType b)
{
    return static_cast<NumericType>(static_cast<int>(a) | static_cast<int>(b));
}

inline NumericType operator &(NumericType a, NumericType b)
{
    return static_cast<NumericType>(static_cast<int>(a) & static_cast<int>(b));
}

inline NumericType& operator |=(NumericType& a, NumericType b)
{
    return a= a |b;
}

int main() {
    // your code goes here
    NumericType a=NumericType::PadWithZero;
    a|=NumericType::NegativeSign;
    cout << static_cast<int>(a) ;
    return 0;
}

打印 3.

【讨论】:

  • 如果枚举是在类内部定义的,并且如果你想在类内部声明运算符,则将它们声明为“朋友”,否则编译器会抱怨运算符的参数太多- 然后它认为它们适用于类而不是枚举。或者在类之外声明运算符,但是您必须使用类名限定枚举名称。如果枚举 E 在类 C 中定义,则 operator|对于枚举应在类外声明为“内联 C::E 运算符|(C::E a, C::E b)”,或在类内声明为“友元 E 运算符|(E a, E b)” .
【解决方案2】:

This seems to work for me:

NumericType operator |= (NumericType &a, NumericType b) {
    unsigned ai = static_cast<unsigned>(a);
    unsigned bi = static_cast<unsigned>(b);
    ai |= bi;
    return a = static_cast<NumericType>(ai);
}

但是,您仍然可以考虑为您的 enum 位集合定义一个类:

class NumericTypeFlags {
    unsigned flags_;
public:
    NumericTypeFlags () : flags_(0) {}
    NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {}
    //...define your "bitwise" test/set operations
};

然后,将您的 |&amp; 运算符更改为返回 NumericTypeFlags

【讨论】:

  • 你的类有一个隐藏的内部字段,就像一个枚举。但是它没有常量,所以一旦你遇到一个需要 NumericTypeFlags 的方法,你就不会从你的开发环境中得到任何帮助。
  • @Virtlink:它提供了比普通的unsigned 更好的类型安全性。它比创建具有不在enum 中的值的NumericType 具有更好的语义。接口的实现可以确保只有NumericType兼容的参数用于测试和设置。简而言之,帮助是给类的用户的,但代价是实现者需要做一些工作以使其对用户有所帮助。
  • 您应该 (a) 返回对修改后对象的引用,并且 (b) 始终强制转换为 enumstd::underlying_type,而不是假设某些固定类型始终足以满足所有值可以通过按位添加或形成。
  • @underscore_d:我同意你的看法。我提供的答案并非针对 C++11,并且正确地将值转换为适当的无符号基础类型以进行按位运算会使答案的本质复杂化。无论如何,容器将是捕获标志的更好方法。 OP 应该考虑使用bitset
【解决方案3】:

我厌倦了枚举算术的所有样板,并转向更像这样的习语:

struct NumericType {
    typedef uint32_t type;
    enum : type {
        None                    = 0,

        PadWithZero             = 0x01,
        NegativeSign            = 0x02,
        PositiveSign            = 0x04,
        SpacePrefix             = 0x08
    };
};

这样我仍然可以传递NumericType::type 参数以清楚起见,但我牺牲了类型安全。

我考虑制作一个通用模板类来代替 uint32_t,它会提供算术重载的一个副本,但显然我不允许从类派生枚举,所以无论如何(感谢 C++!)。

【讨论】:

    【解决方案4】:

    为什么要强类型?因为我的书说这是很好的做法。

    那么你的书不是在谈论你的用例。无范围枚举适用于标志类型。

    enum NumericType : int
    {
        None                    = 0,
    
        PadWithZero             = 0x01,
        NegativeSign            = 0x02,
        PositiveSign            = 0x04,
        SpacePrefix             = 0x08
    };
    

    【讨论】:

      【解决方案5】:

      通过组合不同的值来生成新的、未定义的值,你完全违背了强类型范式。

      看起来您正在设置完全独立的单个标志位。在这种情况下,将您的位组合成一个数据类型是没有意义的,这样的组合会产生一个未定义的值。

      您应该确定标志数据的大小(charshortlonglong long)并随它滚动。但是,您可以使用特定类型来测试、设置和清除标志:

      typedef enum
      {
          PadWithZero             = 0x01,
          NegativeSign            = 0x02,
          PositiveSign            = 0x04,
          SpacePrefix             = 0x08
      } Flag;
      
      typedef short Flags;
      
      void SetFlag( Flags & flags, Flag f )
      {
          flags |= static_cast<Flags>(f);
      }
      
      void ClearFlag( Flags & flags, Flag f )
      {
          flags &= ~static_cast<Flags>(f);
      }
      
      bool TestFlag( const Flags flags, Flag f )
      {
          return (flags & static_cast<Flags>)(f)) == static_cast<Flags>(f);
      }
      

      这是非常基本的,当每个标志只有一个位时就可以了。对于屏蔽标志,它有点复杂。有一些方法可以将位标志封装到强类型类中,但它确实是值得的。在你的情况下,我不相信它是。

      【讨论】:

      • 我这样做的原因:这就是它在强类型 C# 中的工作方式:一个枚举只有一个结构,其基础类型的字段,以及在其上定义的一堆常量。但它可以有任何适合枚举隐藏字段的整数值。而且似乎 C++ 枚举的工作方式完全相同。在这两种语言中,强制转换都需要从 enum 到 int,反之亦然。但是,在 C# 中,按位运算符默认是重载的,而在 C++ 中则不是。顺便说一句...typedef enum { } Flag 不是枚举的 C++11 语法:enum class Flag { }
      • @paddy - 这种事情很常见。枚举类型可以表示的值不限于命名的枚举数;它们可以是适合枚举位的任何值(松散地说)。在这里必须为所有可能的组合提供名称将是乏味的。
      • @ereOn - 当然,如果您将枚举类型视为一个集合,您会得到不属于枚举类型属性的约束。当然,您可以限制枚举类型的使用以匹配该限制模型。这并不意味着使用他们全部能力的人正在滥用他们。该标准经过精心编写,完全允许这种用途。
      • 这可能总是注定要引起辩论的。在我看来,枚举的语义是提供 distinct 值,这些值是该类型的 only possible 值。我仍然认为,虽然将各个位声明为枚举是完全合法的,但使用 same type 来存储这些位的组合在语义上是不正确的。当你破坏语义时,你就违背了强类型。所以你必须在两者之间做出选择。您不能将损坏的语义与强类型结合起来。
      • @paddy:虽然您有权发表自己的意见,但 C++ 委员会明确且有意地扩展了枚举范围以涵盖所有二进制组合,但自动遗漏了 operator|。这里的逻辑是,当且仅当它有意义时,您应该能够提供 operator|,但您不需要为 20 个标志组合拼出一百万个名称。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-04
      • 1970-01-01
      • 1970-01-01
      • 2017-12-28
      • 2010-11-24
      • 2011-01-20
      相关资源
      最近更新 更多