Flags, enum (C)
Asked Answered
B

6

19

I'm not very used to programming with flags, but I think I just found a situation where they'd be useful:

I've got a couple of objects that register themselves as listeners to certain events. What events they register for is dependent on a variable that is sent to them when they are constructed. I think a nice way to do this would be to send bitwise OR connected variables, like such: TAKES_DAMAGE | GRABBABLE | LIQUID, etc. Then, in the constructor, the object can check what flags are set and register it self as listener for the ones that are.

But this is where I get confused. Preferably, the flags would be in an enum. But that is also a problem. If we have got these flags:

enum
{
    TAKES_DAMAGE,/* (0) */
    GRABBABLE, /* (1) */
    LIQUID, /* (2) */
    SOME_OTHER /* (3) */
};

Then sending the flag SOME_OTHER (3) will be the same as sending GRABBABLE | LIQUID, will it not?

How exactly do you deal with this stuff?

Bakke answered 27/10, 2009 at 14:40 Comment(0)
D
62

Your enumeration needs to be powers of two :

enum
{
    TAKES_DAMAGE = 1,
    GRABBABLE = 2,
    LIQUID = 4,
    SOME_OTHER = 8
};

Or in a more readable fashion :

enum
{
    TAKES_DAMAGE = 1 << 0,
    GRABBABLE = 1 << 1,
    LIQUID = 1 << 2,
    SOME_OTHER = 1 << 3
};

Why ? Because you want to be able to combine flags with no overlapping, and also be able to do this:

if(myVar & GRABBABLE)
{
    // grabbable code
}

... Which works if the enumeration values look like this :

 TAKES_DAMAGE: 00000001
 GRABBABLE:    00000010
 LIQUID:       00000100
 SOME_OTHER:   00001000

So, say you've set myVar to GRABBABLE | TAKES_DAMAGE, here's how it works when you need to check for the GRABBABLE flag:

 myVar:     00000011
 GRABBABLE: 00000010 [AND]
 -------------------
            00000010 // non-zero => converts to true

If you'd set myVar to LIQUID | SOME_OTHER, the operation would have resulted in :

 myVar:     00001100
 GRABBABLE: 00000010 [AND]
 -------------------
            00000000 // zero => converts to false
Dermatologist answered 27/10, 2009 at 14:42 Comment(5)
@jldupont - I trust you meant "shiFt operator"? 8v)Fibrilla
Don't think I've gotten 5 replies simultaneously before. o.o Thanks for the excellent explanation and solution.Bakke
Hooray! Now I have a new way to talk about C++ stream insertion and extraction: cout << "that stinks!";Agnesagnese
if you dislike the shift operator method, it might be preferable to use hexadecimal rather than decimal: TAKES_DAMAGE=0x0001, GRABBABLE=0x0002, LIQUID=0x0004, GAS=0x0008, PUSHABLE=0x0010, INFLICTS_DAMAGE=0x0020, OTHER=0x0040 etc. That way it keeps looking pretty with all values aligned in code.Septarium
never thought about it, but this notation is nice : 1 << 1 I used to do it in hexa notation : 0x0001, 0x0002, ... 0x0008, 0x0010, etc..Pour
B
32

another way of storing flags is to not bother with the underlying type at all. when using an enum, the enum values are stored by default into an unsigned int, which is 32 bits on a common computer. this gives you with only 32 possible flags: while certainly much, there are some cases where it is not sufficient.

now you can define your flag set this way:

typedef struct
{
    int takes_damage : 1;
    int grabbable    : 1;
    int liquid       : 1;
    int some_other   : 1;
} flags;

if you never encountered this, the ': 1' part tells the compiler to only use 1 bit to store this struct member.

now you can define a variable to hold the flags, and work with those flags:

flags myflags = {1,0,0,1}; // defines a variable holding a set of flags, with an initial value of takes_damage & some_other

myflags.liquid = 1; // change the flags to include the liquid

if ( myflags.takes_damage ) // test for one flag
    apply_damage();
if ( myflags.liquid && myflags.some_other ) // test for multiple flags
    show_strange_behavior();

this method allows you to define any number of flags, without limitation, and you can extend your flag set at any time without fearing an overflow. the drawback is that testing a subset of the flags is more cumbersome and necessitate more code.

Bushranger answered 27/10, 2009 at 16:1 Comment(9)
Interesting. First time I encounter this method. Would be interested in reading comments of more knowledgeable people than me about the benefits and drawbacks compared to the traditional power of two enums. Does it work with all compilers and all architectures? What are the pitfalls?Emalee
@Emalee i would be interested in more comments too. it is totally standard compliant so it should work with any compiler. the only pitfalls i can think of are: 1. if the structure is not packed, a flag set may take a big amount of memory, much of which is unused. 2. you cannot specify multiple flag values at once, like myflags = grabbable & liquid (however, this is possible by using a union containing the struct and an int value, but this imposes a lot more constraints, is more compiler-dependent and is not the subject of this post)Bushranger
Thanks! Another issue (which is connected with the fact that you can't test a subset of the flags, as you point out), is that you can't easily test if two flag variables are equal. (ie: you can't do: if (myflags == someOtherFlags) {...} ). A bit of a blocker for me, going to stick with plain old enums...Emalee
Where does the C standard say that an enum is unsigned? C99 6.4.4.3p2 says "An identifier declared as an enumeration constant has type int".Londalondon
Using signed bitfields of width 1 only works on two-complements implementations. On sign-magnitude implementations all possible values are 0. There might even be trap representations for the value that doesn't have all-zero bits.Londalondon
@RolandIllig then use an unsigned int of width 1 for each bit in the struct. although your point is valid, i do not know of any platform using sign-magnitude to represent integers. can you provide us with an example of a platform using sign-magnitude ?Bushranger
@RolandIllig regarding your comment about C99 6.4.4.3p2, ok then, the underlying type is signed, that still only gives us 32 bits to work with (on 32 bits platform, which were quite common when that answer was written, more than 10 years ago). anyway, be the underlying type signed or unsigned, my point was to use a struct and not an enum in order to avoid common enum shortcomings in the use case described in the original question.Bushranger
Why would you not just use char for the struct members? Does limited an int to 1 byte do anything differently?Skep
@Skep i come from a language where a char and an int are not the same and cannot mix. nowadays, i would use uint8_t (from <stdint.h>)Bushranger
F
7

Yes. Instead, make your enum members powers of 2:

enum
{
    TAKES_DAMAGE = (1 << 0),
    GRABBABLE = (1 << 1),
    LIQUID = (1 << 2),
    SOME_OTHER = (1 << 3)
};
Fibrilla answered 27/10, 2009 at 14:42 Comment(1)
To the downvoter: perhaps you'd like to explain what you found not useful about this answer?Fibrilla
S
4

You should make the flags only powers of two, i.e. each is a bit in whatever data type you're storing this in, and nothing overlaps when you bitwise OR.

Sheave answered 27/10, 2009 at 14:42 Comment(0)
W
4

Can't you just set the values in the enum?

enum {
 TAKES_DAMAGE = 1,
 GRABBABLE    = 2,
 LIQUID       = 4
}

Afterwards, just perfom bit-wise OR on them.

Wheelhorse answered 27/10, 2009 at 14:42 Comment(0)
E
3

you need

enum
{
    TAKES_DAMAGE = 1,
    GRABBABLE = 2,
    LIQUID = 4,
    SOME_OTHER = 8
};
Elexa answered 27/10, 2009 at 14:42 Comment(1)
I think you need to fix the syntax on this one. (I know, the same error was in the question. That's been fixed now, though.)Fibrilla

© 2022 - 2024 — McMap. All rights reserved.