Являются ли перечисления каноническим способом реализации битовых флагов?
В настоящее время я использую перечисления для представления состояния в маленьком игровом эксперименте. Я объявляю их так:
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/...