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

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

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


person Yohaï-Eliel Berreby    schedule 16.06.2014    source источник
comment
Есть это, но я не уверен, что это канонический способ делать что-то ... stackoverflow.com/a/439004/541686 < / а>   -  person user541686    schedule 17.06.2014
comment
Слишком тяжело для меня. Мне нужен самый легкий из возможных методов, используя только чистый C ++ или стандартную библиотеку, если это необходимо. Без повышения. Моя цель здесь - минимизировать использование памяти и ЦП, потому что я создаю игру, а задержка в миллисекундах будет означать огромное падение количества кадров в секунду.   -  person Yohaï-Eliel Berreby    schedule 17.06.2014
comment
Я не могу понять никакого Boost, но я не могу понять озабоченность по поводу использования памяти или ЦП во время run, поскольку все это константы времени компиляции, а не значения времени выполнения ...   -  person user541686    schedule 17.06.2014
comment
Я не использую Boost, но то, что я вижу в вашей ссылке, - это использование функций, специфичных для Boost, преобразований в специальные классы, такие как Abort или _2 _... ненужная обработка.   -  person Yohaï-Eliel Berreby    schedule 17.06.2014
comment
Я считаю, что единственная необходимая обработка - это если вы хотите получить строку, связанную с перечислением, что в любом случае вам не следует делать очень часто в игровом движке. В противном случае перечисление является общедоступным внутри класса и похоже на любое другое перечисление ...   -  person user541686    schedule 17.06.2014
comment
@Mehrdad: Похоже, это прямое перечисление с последовательными значениями. Можно ли его использовать для указания перечисления битовых флагов?   -  person Fred Larson    schedule 17.06.2014
comment
@FredLarson: Упс! Вы правы, я почему-то совсем забыл об этом, когда искал ... моя плохая ...   -  person user541686    schedule 17.06.2014
comment
@Gluttton Я бы проголосовал за тебя дважды, если бы мог :)   -  person Yohaï-Eliel Berreby    schedule 17.06.2014
comment
См. мой ответ на этот другой вопрос для моего предложения о том, как определять наборы битовых флагов.   -  person Cris Luengo    schedule 08.01.2020


Ответы (3)


Я считаю, что ваш подход правильный (за исключением нескольких вещей):
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);
}
person Gluttton    schedule 16.06.2014
comment
Почему mState &= ~State::MoveUp;? Что именно он делает? И зачем заставлять мое перечисление наследовать от char? По поводу оператора |=: отредактирую свой вопрос, у меня проблемы с его использованием. - person Yohaï-Eliel Berreby; 17.06.2014
comment
То же, что и вычитание. Я имею в виду, что после анализа флага его нужно выключить. - person Gluttton; 17.06.2014
comment
Но я не хочу его выключать. Я анализирую это в нескольких модулях своей игры. Кстати, мне наконец удалось реализовать оператор |=, используя приведение в стиле C в перегрузке. Вы не ответили на мой вопрос о наследовании от char. - person Yohaï-Eliel Berreby; 17.06.2014
comment
Очевидно, это не работает без перегрузки оператора. - person Yohaï-Eliel Berreby; 17.06.2014
comment
@filsmick: Этот синтаксис enum Value : char на самом деле не является наследованием, это синтаксис C ++ 11, который сообщает компилятору, что вы хотите, чтобы интегральный тип, на котором основано перечисление, был char. В предыдущих версиях C ++ вам не разрешалось указывать это. В данном случае это означает ваш sizeof(Value) == sizeof(char) - person Dave S; 17.06.2014
comment
Спасибо, теперь я понимаю. Однако использовать char здесь следует с осторожностью, поскольку sizeof(char) обычно равно 1, что означает, что вы можете представить только 8 значений в перечислении вместо 32, если вы используете int. Поправьте меня если я ошибаюсь. - person Yohaï-Eliel Berreby; 17.06.2014
comment
@filsmick: sizeof(char) равно 1 по определению, но это не обязательно означает, что это 8 бит. На некоторых платформах может быть больше. - person Fred Larson; 17.06.2014
comment
@FredLarson Я знал, что кто-то скажет это, когда я написал свой комментарий. Вот почему я сказал обычно :) - person Yohaï-Eliel Berreby; 17.06.2014
comment
@filsmick, я бы хотел продемонстрировать подход к управлению памятью. Очевидно, вам следует использовать более подходящий тип int16_t, int32_t или int64_t. - person Gluttton; 17.06.2014
comment
@Gluttton Насчет _t типов вы правы, не стоит полагаться на стандартный размер. Между прочим, я произвел быстрый поиск и увидел, что это не строго запрещено просить проголосовать, так что ... поскольку я прав, не могли бы вы проголосовать за комментарий, в котором я сказал что-то правильно? - person Yohaï-Eliel Berreby; 17.06.2014
comment
@filsmick: обычно неверно. sizeof(char) ВСЕГДА равен 1, независимо от того, сколько бит он использует. - person Fred Larson; 17.06.2014
comment
@FredLarson Правда? Я не знал этого, я думал, что он вернул реальный размер на используемой платформе - спасибо, что указали на это. - person Yohaï-Eliel Berreby; 17.06.2014
comment
Он действительно возвращает фактический размер наименьшего адресуемого блока на используемой платформе, который является определением 1 байт / char. Если это 10 бит, пусть будет так. - person Mooing Duck; 07.01.2017

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

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

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

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

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

person Carl    schedule 16.06.2014
comment
Спасибо за объяснение, как использовать битовые наборы для битовых флагов! Мне было бы любопытно увидеть тест этого и битового перечисления. - person Yohaï-Eliel Berreby; 17.06.2014

Честно говоря, я не думаю, что для них есть единый шаблон.

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

person user541686    schedule 16.06.2014