C++ 使枚举类型支持位运算

在 C++ 编程中,经常需要用枚举类型表示各种选项, 而这些选项还可能需要各种组合。 比如在 Qt 编程中, 需要经常写这样的代码。

   MainWindow w;
   w.setWindowFlags( Qt::WindowMinMaxButtonsHint | Qt::WindowMaxmizeButtonHint );

这要求枚举类型可以参与位运算, 并且位运算得到的结果还得是枚举类型。 也就是说, 我们需要满足这样的需求:

class AClass{
public:
    enum TestFlag
    {
        Enum1 = 1, Enum2 = 2, Enum3 = 3
    };
};

void EnumTest::testFlags()
{
    AClass::TestFlags result = AClass::Enum1 | AClass::Enum2;

    assert(result == (1 | 2));
}

先来看看原生 C++ 是否支持这种运算。 如果在 Qt Creator 里写出这段代码, Clang 静态语法分析会提示:

error: cannot initialize a variable of type 'AClass::TestFlag' with an rvalue of type 'int'

这说明, 在参与位运算时, TestFlag枚举型变量被转型为 int类型。 但是没有从 int类型转换为TestFlag类型的方法。

原生 C++ 的枚举类型还有一个问题, 那就是可以把不同的枚举变量放在一起运算。 请看以下代码:

class AClass{
public:
    enum TestFlag
    {
        Enum1 = 1, Enum2 = 2, Enum3 = 3
    };
};

class BClass
{
public:
    enum TestFlags
    {
      Enum1, Enum2, Enum3
    };
};

void EnumTest::testFlags()
{
    int result = AClass::Enum1 | BClass::Enum2;
}

这个程序是可以通过编译的, 但是枚举值都是带有一定语义的,result到底代表了什么含义呢? 这样做讲不出一点道理。

为了解决上面的问题,需要自己设计一个枚举类来增强 C++ 语言。 大概的思路是, 在枚举类型外套上一层,并进行运算符重载。

template <typename Enum>
class MqFlags
{
    using Self = MqFlags<Enum>;
    using EnumType = Enum;
public:
    MqFlags(const MqFlags& other)
        : i (other.i)
    {

    }

    MqFlags(const Enum& e)
        : i((int)e)
    {

    }

    MqFlags(const int ival)
        : i(ival)
    {

    }

    operator int() const
    {
        return i;
    }

    MqFlags operator | (const MqFlags& another)
    {
        MqFlags g;
        g.i = another.i | Self::i;
        return g;
    }

    MqFlags operator | (const EnumType f)
    {
        return *this | MqFlags(f);
    }

    MqFlags operator & (const MqFlags& another)
    {
        MqFlags g;
        g.i = another.i & Self::i;
        return g;
    }

    MqFlags operator & (const EnumType f)
    {
        return *this & MqFlags(f);
    }

private:
    int i;
};

这里, int i用于保存枚举类型变量的值,并提供了多种构造函数用于构造,还提供了一个转换函数, 把枚举变量转换为 int 类型变量。 这个类提供了一些算符运算, 用于支持简单的位运算。

在项目中使用这个改进的枚举变量, 除了声明原生枚举变量外, 还需要声明封装类。

class AClass{
public:
    enum TestFlag
    {
        Enum1 = 1, Enum2 = 2, Enum3 = 3
    };

    using TestFlags = MqFlags <TestFlag>;

private:
    TestFlags option;
};

接下来, 使用宏来化简这个封装类的声明。

#define MQ_DECLARE_FLAGS(enumType, newEnumType) using newEnumType = MqFlags<enumType>

class AClass{
public:
    enum TestFlag
    {
        Enum1 = 1, Enum2 = 2, Enum3 = 3
    };

    MQ_DECLARE_FLAGS(TestFlag, TestFlags);

private:
    TestFlags option;
};

这就是 Q_FLAGS的实现细节, 我在源码上做了一些化简。 这样, 在有些不引用 qt 库的项目中, 也可以用到这个类、

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容