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

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

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 — есть идеи почему?

10

Решение

Я считаю, что ваш подход правильный (кроме нескольких вещей):
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);
}

и так как вы используете C ++ 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);
}
3

Другие решения

STL содержит станд :: 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, что обычно является одной из тех вещей, которые вам понадобятся при использовании состояния внутри различных структур данных.

РЕДАКТИРОВАТЬ:
Есть одно ограничение для std :: bitset, и это тот факт, что вам нужно знать максимальное количество битов в вашем наборе битов во время компиляции. Тем не менее, это так же и в случае с перечислениями.

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

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

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

4

Если честно я там не думаю является последовательный образец для них.

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

3

Смотрите мой ответ на этот другой вопрос для моего предложения о том, как определить наборы битовых флагов: https://stackoverflow.com/a/41516273/7328782

0