Bit Flags
Category | C++ |
---|
Overview
Flags are values that can be combined using bitwise-operations.
They are often implemented as enum types.
C
In C, it's common to define flags as preprocessor defines.
#define FileModeRead = 0x1
#define FileModeWrite = 0x2
#define FileModeAppend = 0x4
Or as constant integers.
static const unsigned FileModeRead = 0x1;
static const unsigned FileModeWrite = 0x2;
static const unsigned FileModeAppend = 0x4;
C++
Enum type
In C++, enums are not scoped, and it's a best practice to add a prefix to each member.
enum PrimitiveType
{
PrimitiveType_Point,
PrimitiveType_Line,
PrimitiveType_LineStrip,
PrimitiveType_Triangle,
PrimitiveType_TriangleStrip,
};
PrimitiveType type = PrimitiveType_Triangle;
Enum types have an integer underlying type.
By default, the first constant is set to 0, and the value increases with each constant sequentially.
It's also possible to specify the value for each constant instead.
enum PrimitiveType
{
PrimitiveType_Point = 0,
PrimitiveType_Line = 1,
PrimitiveType_LineStrip = 2,
PrimitiveType_Triangle = 3,
PrimitiveType_TriangleStrip = 4,
};
PrimitiveType type = PrimitiveType_Triangle;
Enum class type
Since C++11, enum class
has been introduced and provide a scope for enum values.
enum class PrimitiveType
{
Point,
Line,
LineStrip,
Triangle,
TriangleStrip,
};
PrimitiveType type = PrimitiveType::Triangle;
Flags with enum
Enum types can be used as flags by specifying power-of-two values.
enum FileMode
{
FileMode_Read = 1,
FileMode_Write = 2,
FileMode_Append = 4,
};
The values can be specied sequentially using the bitwise-left operator <<
.
enum class FileMode
{
FileMode_Read = (1 << 0),
FileMode_Write = (1 << 1),
FileMode_Append = (1 << 2),
};
The values can be combined using a bitwise-or operation.
int mode = (FileMode_Read | FileMode_Write);
The result has to be stored into an int
value, but we can do a static_cast
to the enum type.
FileMode mode = static_cast<FileMode>(FileMode_Read | FileMode_Write);
We can check if a flag contains a specific value using a bitwise-and operation.
bool canRead = ((mode & FileMode_Read) != 0);
int mode = FileMode_Read | PrimitiveType_TriangleStrip;
In this case, mode
will have an integer value of 4, which is not the expected result.
When the result is cast to FileMode
, it will have an invalid value that can create an undefined behavior.
FileMode mode = static_cast<FileMode>(FileMode_Read | PrimitiveType_TriangleStrip);
Flags with enum class
We can define flags using the enum class type to increase the type safety.
enum class FileMode
{
Read = (1 << 0),
Write = (1 << 1),
Append = (1 << 2),
};
FileMode mode = (FileMode::Read | FileMode::Write);
bool canRead = ((mode & FileMode::Read) != 0);
FileMode mode = (static_cast<FileMode>(static_cast<int>(FileMode::Read) | static_cast<int>(FileMode::Write)));
bool canRead = ((static_cast<int>(mode) & static_cast<int>(FileMode::Read)) != 0);
FileMode mode = static_cast<FileMode>(static_cast<int>(FileMode::Read) | static_cast<int>(PrimitiveType::TriangleStrip));
It can still produce invalid values.
Flags class
We can define custom operators for each bitwise operation.
We use std::underlying_type
instead of a hardcoded integer type.
Bitwise operations
Flags operator &(Flags lhs, Flags rhs)
{
return static_cast<Flags> (
static_cast<std::underlying_type<Flags>::type>(lhs) &
static_cast<std::underlying_type<Flags>::type>(rhs)
);
}
Flags operator |(Flags lhs, Flags rhs)
{
return static_cast<Flags> (
static_cast<std::underlying_type<Flags>::type>(lhs) |
static_cast<std::underlying_type<Flags>::type>(rhs)
);
}
Flags operator ^(Flags lhs, Flags rhs)
{
return static_cast<Flags> (
static_cast<std::underlying_type<Flags>::type>(lhs) ^
static_cast<std::underlying_type<Flags>::type>(rhs)
);
}
Flags operator ~(Flags value)
{
return static_cast<Flags> (
~static_cast<std::underlying_type<Flags>::type>(value)
);
}
Bitwise assignments
Flags& operator &=(Flags& lhs, Flags rhs)
{
lhs = static_cast<Flags> (
static_cast<std::underlying_type<Flags>::type>(lhs) &
static_cast<std::underlying_type<Flags>::type>(rhs)
);
return lhs;
}
Flags& operator |=(Flags& lhs, Flags rhs)
{
lhs = static_cast<Flags> (
static_cast<std::underlying_type<Flags>::type>(lhs) |
static_cast<std::underlying_type<Flags>::type>(rhs)
);
return lhs;}
Flags& operator ^=(Flags& lhs, Flags rhs)
{
lhs = static_cast<Flags> (
static_cast<std::underlying_type<Flags>::type>(lhs) ^
static_cast<std::underlying_type<Flags>::type>(rhs)
);
return lhs;
}