Are enums the canonical way to implement bit flags?

后端 未结 3 2020
死守一世寂寞
死守一世寂寞 2021-02-19 04:30

Currently I\'m using enums to represent a state in a little game experiment. I declare them like so:

namespace State {
  enum Value {
    MoveUp = 1 << 0,          


        
相关标签:
3条回答
  • 2021-02-19 04:46

    The STL contains std::bitset, which you can use for precisely such a case.

    Here is just enough code to illustrate the concept:

    #include <iostream>
    #include <bitset>
    
    class State{
    public:
        //Observer
        std::string ToString() const { return state_.to_string();};
        //Getters
        bool MoveUp()    const{ return state_[0];}; 
        bool MoveDown()  const{ return state_[1];}; 
        bool MoveLeft()  const{ return state_[2];}; 
        bool MoveRight() const{ return state_[3];}; 
        bool Still()     const{ return state_[4];}; 
        bool Jump()      const{ return state_[5];}; 
        //Setters
        void MoveUp(bool on)    {state_[0] = on;}
        void MoveDown(bool on)  {state_[1] = on;}
        void MoveLeft(bool on)  {state_[2] = on;}
        void MoveRight(bool on) {state_[3] = on;}
        void Still(bool on)     {state_[4] = on;}
        void Jump(bool on)      {state_[5] = on;}
    private:
        std::bitset<6> state_;
    };
    
    
    int main() {
        State s;
        auto report = [&s](std::string const& msg){
            std::cout<<msg<<" "<<s.ToString()<<std::endl;
        };
        report("initial value");
        s.MoveUp(true);
        report("move up set");
        s.MoveDown(true);
        report("move down set");
        s.MoveLeft(true);
        report("move left set");
        s.MoveRight(true);
        report("move right set");
        s.Still(true);
        report("still set");
        s.Jump(true);
        report("jump set");
        return 0;
    }
    

    Here's it working: http://ideone.com/XLsj4f

    The interesting thing about this is that you get std::hash support for free, which is typically one of the things you would need when using state inside various data structures.

    EDIT: There is one limitation to std::bitset and that is the fact that you need to know the maximum number of bits in your bitset at compile time. However, that is the same case with enums anyway.

    However, if you don't know the size of your bitset at compile time, you can use boost::dynamic_bitset, which according to this paper (see page 5) is actually really fast. Finally, according to Herb Sutter, std::bitset was designed to be used in cases you would normally want to use std::vector.

    That said, there really is no substitute for real world tests. So if you really want to know, profile. That will give you performance numbers for a context that you care about.

    I should also mention that std::bitset has an advantage that enum does not - there is no upper limit on the number of bits you can use. So std::bitset<1000> is perfectly valid.

    0 讨论(0)
  • 2021-02-19 04:48

    To be honest I don't think there is a consistent pattern for them.

    Just look at std::ios_base::openmode and std::regex_constants::syntax_option_type as two completely different ways of structuring it in the standard library -- one using a struct, the other using an entire namespace. Both are enums all right, but structured differently.
    Check your standard library implementation to see the details of how the above two are implemented.

    0 讨论(0)
  • 2021-02-19 04:54

    I believe that your approach is right (except several things):
    1. You can explicitly specify underlying type to save memory;
    2. You can not use unspecified enum values.

    namespace State {
      enum Value : char {
        None      = 0,
        MoveUp    = 1 << 0, // 00001 == 1
        MoveDown  = 1 << 1, // 00010 == 2
        MoveLeft  = 1 << 2, // 00100 == 4
        MoveRight = 1 << 3, // 01000 == 8
        Still     = 1 << 4, // 10000 == 16
        Jump      = 1 << 5
      };
    }
    

    and:

    State::Value state = State::Value::None;
    state = State::Value(state | State::MoveUp);
    if (mState & State::MoveUp) {
      movement.y -= mPlayerSpeed;
    }
    

    about overloading:

    inline State::Value& operator|=(State::Value& a, State::Value b) {
        return a = static_cast<State::Value> (a | b);
    }
    

    and since you use C++11, you should use constexpr every were is possible:

    inline constexpr State::Value operator|(State::Value a, State::Value b) {
        return a = static_cast<State::Value> (a | b);
    }
    
    inline constexpr State::Value operator&(State::Value a, State::Value b) {
        return a = static_cast<State::Value> (a & b);
    }
    
    0 讨论(0)
提交回复
热议问题