代码之家  ›  专栏  ›  技术社区  ›  luke

C++中的类型安全(R)位标志?

  •  23
  • luke  · 技术社区  · 14 年前

    在修改一些旧的c++代码时,我遇到了几个 bitflags 定义为枚举。

    enum FooFlags
    {
        FooFlag1 = 1 << 0,
        FooFlag2 = 1 << 1,
        FooFlag3 = 1 << 2
        // etc...
    };
    

    这并不少见,但让我烦恼的是,一旦开始组合标志,就会丢失类型信息。

    int flags = FooFlag1 | FooFlag2;   // We've lost the information that this is a set of flags relating to *Foo*
    

    一些搜索显示我不是 only one 为此烦恼。

    另一种方法是将标志声明为定义或常量积分,这样按位操作就不会转换类型(可能)。问题是它允许我们的位集通过int或其他枚举与不相关的标志混合。

    我很熟悉 std::bitset boost::dynamic_bitset ,但两者都不是为解决我的问题而设计的。我要找的是类似C#的东西 FlagsAttribute .

    我的问题是,对于(更多)类型安全的位标志集,还有什么其他解决方案?

    我会在下面发布我自己的解决方案。

    3 回复  |  直到 7 年前
        1
  •  22
  •   Johannes Schaub - litb    14 年前

    可以重载返回正确类型结果的枚举类型的运算符。

    inline FooFlags operator|(FooFlags a, FooFlags b) {
      return static_cast<FooFlags>(+a | +b);
    }
    

    需要注意的是,为了在理论上是安全的,您应该手动声明可能的最大值,这样枚举类型的范围就可以保证捕获所有组合。

    • 实际上,这并不需要:枚举的范围将始终能够捕获所有组合,因为枚举范围的最大正值总是 (2^N)-1 第一次 N 能够表示最高的枚举数。这个值的所有位都是1。
        2
  •  13
  •   luke    11 年前

    下面是我自己的解决方案,使用当前版本的VS2010允许的c++0x元素:

    #include <iostream>
    #include <numeric>
    #include <string>
    
    #include <initializer_list>
    
    template <typename enumT>
    class FlagSet
    {
        public:
    
            typedef enumT                     enum_type;
            typedef decltype(enumT()|enumT()) store_type;
    
            // Default constructor (all 0s)
            FlagSet() : FlagSet(store_type(0))
            {
    
            }
    
            // Initializer list constructor
            FlagSet(const std::initializer_list<enum_type>& initList)
            {
                // This line didn't work in the initializer list like I thought it would.  It seems to dislike the use of the lambda.  Forbidden, or a compiler bug?
                flags_ = std::accumulate(initList.begin(), initList.end(), store_type(0), [](enum_type x, enum_type y) { return x | y; })
            }
    
            // Value constructor
            explicit FlagSet(store_type value) : flags_(value)
            {
    
            }
    
            // Explicit conversion operator
            operator store_type() const
            {
                return flags_;
            }
    
            operator std::string() const
            {
                return to_string();
            }
    
            bool operator [] (enum_type flag) const
            {
                return test(flag);
            }
    
            std::string to_string() const
            {
                std::string str(size(), '0');
    
                for(size_t x = 0; x < size(); ++x)
                {
                    str[size()-x-1] = (flags_ & (1<<x) ? '1' : '0');
                }
    
                return str;
            }
    
            FlagSet& set()
            {
                flags_ = ~store_type(0);
                return *this;
            }
    
            FlagSet& set(enum_type flag, bool val = true)
            {
                flags_ = (val ? (flags_|flag) : (flags_&~flag));
                return *this;
            }
    
            FlagSet& reset()
            {
                flags_ = store_type(0);
                return *this;
            }
    
            FlagSet& reset(enum_type flag)
            {
                flags_ &= ~flag;
                return *this;
            }
    
            FlagSet& flip()
            {
                flags_ = ~flags_;
                return *this;
            }
    
            FlagSet& flip(enum_type flag)
            {
                flags_ ^= flag;
                return *this;
            }
    
            size_t count() const
            {
                // http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
    
                store_type bits = flags_;
                size_t total = 0;
                for (; bits != 0; ++total)
                {
                    bits &= bits - 1; // clear the least significant bit set
                }
                return total;
            }
    
            /*constexpr*/ size_t size() const   // constexpr not supported in vs2010 yet
            {
                return sizeof(enum_type)*8;
            }
    
            bool test(enum_type flag) const
            {
                return (flags_ & flag) > 0;
            }
    
            bool any() const
            {
                return flags_ > 0;
            }
    
            bool none() const
            {
                return flags == 0;
            }
    
        private:
    
            store_type flags_;
    
    };
    
    template<typename enumT>
    FlagSet<enumT> operator & (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
    {
        return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) & FlagSet<enumT>::store_type(rhs));
    }
    
    template<typename enumT>
    FlagSet<enumT> operator | (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
    {
        return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) | FlagSet<enumT>::store_type(rhs));
    }
    
    template<typename enumT>
    FlagSet<enumT> operator ^ (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
    {
        return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) ^ FlagSet<enumT>::store_type(rhs));
    }
    
    template <class charT, class traits, typename enumT>
    std::basic_ostream<charT, traits> & operator << (std::basic_ostream<charT, traits>& os, const FlagSet<enumT>& flagSet)
    {
        return os << flagSet.to_string();
    }
    

    接口是按照 std::bitset . 我的目标是实现类型安全和最小(如果有的话)开销的c++方法。我欢迎任何关于我的执行情况的反馈。

    下面是一个简单的例子:

    #include <iostream>
    
    enum KeyMod
    {
        Alt     = 1 << 0,  // 1
        Shift   = 1 << 1,  // 2
        Control = 1 << 2   // 4
    };
    
    void printState(const FlagSet<KeyMod>& keyMods)
    {
        std::cout << "Alt is "     << (keyMods.test(Alt)     ? "set" : "unset") << ".\n";
        std::cout << "Shift is "   << (keyMods.test(Shift)   ? "set" : "unset") << ".\n";
        std::cout << "Control is " << (keyMods.test(Control) ? "set" : "unset") << ".\n";
    }
    
    int main(int argc, char* argv[])
    {
        FlagSet<KeyMod> keyMods(Shift | Control);
    
        printState(keyMods);
    
        keyMods.set(Alt);
        //keyMods.set(24);    // error - an int is not a KeyMod value
        keyMods.set(Shift);
        keyMods.flip(Control);
    
        printState(keyMods);
    
        return 0;
    }
    
        3
  •  7
  •   Christian    12 年前

    我想我可以为 enum class

    FooFlags operator|(FooFlags a, FooFlags b)
    {
      typedef std::underlying_type<FooFlags>::type enum_type;
      return static_cast<FooFlags>(static_cast<enum_type>(a) | static_cast<enum_type>(b));
    }
    

    如果你的c++11版本支持它,我想这将是 constexpr

    推荐文章