Являются ли перечисления каноническим способом реализации битовых флагов?

В настоящее время я использую перечисления для представления состояния в маленьком игровом эксперименте. Я объявляю их так:

namespace State {
  enum Value {
    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
  };
}

Чтобы я мог использовать их таким образом:

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

Но мне интересно, правильно ли это реализовать битовые флаги. Разве нет специального контейнера для бит-флагов? Я слышал о std::bitset, это то, что я должен использовать? Знаете ли вы что-то более эффективное?
Правильно ли я делаю это?


Я забыл указать, что я перегружал основных операторов моего перечисления:
inline State::Value operator|(State::Value a, State::Value b)
{ return static_cast<State::Value>(static_cast<int>(a) | static_cast<int>(b)); }

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


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

Мне пришлось использовать C-стиль для |=, он не работал с static_cast - любая идея, почему?

Ответ 1

Я считаю, что ваш подход прав (за исключением нескольких вещей):
1. Вы можете явно указать базовый тип для сохранения памяти.
2. Вы не можете использовать неопределенные значения перечисления.

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
  };
}

и

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

о перегрузке:

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

и поскольку вы используете С++ 11, вы должны использовать constexpr, что возможно:

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);
}

Ответ 2

STL содержит std:: bitset, который вы можете использовать для такого случая.

Вот достаточно кода, чтобы проиллюстрировать концепцию:

#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;
}

Здесь он работает: http://ideone.com/XLsj4f

Интересно, что вы получаете бесплатную поддержку std:: hash, которая обычно является одной из вещей, которые вам нужны при использовании состояния внутри различных структур данных.

EDIT: Существует одно ограничение для std:: bitset, и это тот факт, что вам нужно знать максимальное количество бит в вашем битете во время компиляции. Тем не менее, это тот же случай с перечислениями в любом случае.

Однако, если вы не знаете размер вашего битового набора во время компиляции, вы можете использовать boost:: dynamic_bitset, который согласно к этой статье (см. стр. 5) действительно очень быстро. Наконец, согласно Herb Sutter, std:: bitset был разработан для использования в случаях, когда вы обычно хотите использовать std::vector.

Тем не менее, действительно нет замены для тестов в реальном мире. Поэтому, если вы действительно хотите узнать, профиль. Это даст вам номера производительности для интересующего вас контекста.

Я также должен упомянуть, что std:: bitset имеет преимущество в том, что перечисление не существует - нет верхнего предела количества бит, которое вы можете использовать. Таким образом, std:: bitset < 1000 > отлично работает.

Ответ 3

Если честно, я не думаю, что для них существует последовательная модель.

Просто посмотрите на std::ios_base::openmode и std::regex_constants::syntax_option_type как два совершенно разных способа структурирования в стандартной библиотеке - один с использованием структуры, другой - с использованием всего пространства имен. Оба являются перечислениями в порядке, но структурированы по-разному.
Проверьте стандартную реализацию библиотеки, чтобы узнать подробности того, как реализованы эти два.

Ответ 4

См. мой ответ на этот другой вопрос для моего предложения о том, как определять установки флагов бит: fooobar.com/info/490734/...