14

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

So that I can use them this way:

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

But I'm wondering if this is the right way to implement bit flags. Isn't there a special container for bit flags? I heard about std::bitset, is it what I should use? Do you know something more efficient?
Am I doing it right?


I forgot to point out I was overloading the basic operators of my enum:
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); }

I had to use a C-style cast for the |=, it didn't work with a static_cast - any idea why?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Yohaï-Eliel Berreby
  • 1,165
  • 3
  • 14
  • 25
  • There's this, but I'm not sure if it's the "canonical" way to do things... http://stackoverflow.com/a/439004/541686 – user541686 Jun 16 '14 at 20:14
  • 1
    Too heavy for me. I want the *lightest* method possible, using only pure C++ or the standard library if necessary. No Boost. My goal here is to minimize memory and CPU usage because I'm making a game, and a millisecond of delay would mean a **huge** drop in the number of fps. – Yohaï-Eliel Berreby Jun 16 '14 at 20:16
  • 4
    I can understand "no Boost", but I can't understand the concern about memory or CPU usage at *run* time, since these are all compile-time constants, not run-time values... – user541686 Jun 16 '14 at 20:22
  • I don't use Boost, but what I see in your link is the use of Boost-specific functions, casts to special classes like `Abort` or `Error`... unnecessary processing. – Yohaï-Eliel Berreby Jun 16 '14 at 20:27
  • 1
    I believe the only processing required there is if you want to get the string associated with the enum, which you shouldn't be doing very often in a game engine anyway. Otherwise the enum is public inside the class and it's just like any other enum... – user541686 Jun 16 '14 at 20:33
  • 2
    @Mehrdad: It looks like that does a straight enum with sequential values. Can it be used to specify a bit flag enum? – Fred Larson Jun 16 '14 at 20:36
  • @FredLarson: Whoops! You're right, for some reason I totally forgot about that when looking it up... my bad... – user541686 Jun 16 '14 at 20:39
  • I found an alternative technique using structs of bool - not technically bit flags, but can replace them sometimes. Link: http://stackoverflow.com/a/20916/2754323 – Yohaï-Eliel Berreby Jun 17 '14 at 09:00
  • See [my answer to this other question](https://stackoverflow.com/a/41516273/7328782) for my suggestion on how to define bit flag sets. – Cris Luengo Jan 08 '20 at 16:16

3 Answers3

9

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.

Carl
  • 43,122
  • 10
  • 80
  • 104
8

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);
}
Yohaï-Eliel Berreby
  • 1,165
  • 3
  • 14
  • 25
Gluttton
  • 5,739
  • 3
  • 31
  • 58
  • Why `mState &= ~State::MoveUp;`? What exactly does it do? And why make my enum inherit from char? Regarding the `|=` operator: I'll edit my question, I have trouble using it. – Yohaï-Eliel Berreby Jun 16 '14 at 20:29
  • The same as subtraction. I mean that after analyze of flag you need switch it off. – Gluttton Jun 16 '14 at 20:33
  • But I *don't* want to switch it off. I analyze it in several modules of my game. By the way, I finally managed to implement the `|=` operator by using a C-style cast in the overload. You didn't answer my question regarding the inheritance from `char`. – Yohaï-Eliel Berreby Jun 16 '14 at 20:39
  • Obviously it doesn't work without operator overloading. – Yohaï-Eliel Berreby Jun 16 '14 at 20:39
  • 4
    @filsmick: That syntax `enum Value : char` isn't really inheritance, it is the C++11 syntax to tell the compiler you want the integral type that the enum is based on to be a `char`. In previous versions of C++, you weren't allowed to specify that. In this case, it means your `sizeof(Value) == sizeof(char)` – Dave S Jun 16 '14 at 20:41
  • Thanks, I understand now. However using a `char` here should be done with cautious, since `sizeof(char)` is usually 1, which means you can only represent 8 values in the enum instead of 32 if you use an `int`. Correct me if I'm wrong. – Yohaï-Eliel Berreby Jun 16 '14 at 20:44
  • 4
    @filsmick: `sizeof(char)` is 1 *by definition*, but that doesn't necessarily mean it's 8 bits. It could be more on some platforms. – Fred Larson Jun 16 '14 at 20:54
  • @FredLarson I knew someone would say that when I wrote my comment. That's why I said **usually** :) – Yohaï-Eliel Berreby Jun 16 '14 at 20:57
  • 2
    @filsmick, I'd like demonstrate the approach how to manage memory. Obviously you should use more appropriate type `int16_t`, `int32_t` or `int64_t`. – Gluttton Jun 16 '14 at 20:57
  • @Gluttton You're right about the `_t` types, one should not rely on the standard size. Incidentally, I did a quick search and saw it wasn't strictly forbidden to ask for upvotes, so... since I am right, would you mind upvoting the comment where I said something right? – Yohaï-Eliel Berreby Jun 16 '14 at 20:59
  • 5
    @filsmick: "usually" is incorrect. `sizeof(char)` is ALWAYS 1, no matter how many bits it uses. – Fred Larson Jun 16 '14 at 20:59
  • @FredLarson Really? I didn't know that, I thought it returned the actual size on the platform used - thanks for pointing it out. – Yohaï-Eliel Berreby Jun 16 '14 at 21:01
  • It does return the actual size of the smallest addressable unit on the platform used, which is the definition of 1 byte/`char`. If that's 10 bits, so be it. – Mooing Duck Jan 07 '17 at 00:24
  • I suggest specifying a portable unsigned integer as the underlying type, e.g.: `enum Value : uint32_t {`. Choose a type big enough to hold your largest value. This requires C++11 or newer, and may avoid issues using signed integers with bitwise operations - some of which are undefined behavior. Since the specific values are added to the global namespace, you may want to name them like `Value_None`, `Value_MoveUp`, etc., where `Value` is replaced by something more meaningful. Scoped enums solve this, too, but then you don't inherit the bitwise operators from the unit32_t type. – MikeOnline Jun 03 '23 at 01:48
5

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.

user541686
  • 205,094
  • 128
  • 528
  • 886