Bitwise Operators and Bit Flags

An introduction to the bitwise AND, OR, and XOR operators - and one of their most interesting use cases
invert.jpg
Ryan McCombe
Published

Behind the scenes, every variable and object we create when programming is stored in binary. That is, a series of binary digits (bits) that can have only two possible vales: on or of - typically represented by 1 or 0

Generally, we don’t need to work on this level. Our compilers and interpreters just abstract this away so we’re acting on higher level objects, like numbers and strings.

But, there are some use cases where working at the binary level is the best option. Additionally, there are some operators that help us do that.

This article introduces those operators - typically called bitwise operators.

Then, we will introduce and implement one of the the main scenarios where we’d want to use them: bit flags.

Logical Operators vs Bitwise Operators

We previously saw how the || and && operators can be used to combine booleans in different ways.

These are called the “logical OR” and “logical AND” operators. They have their bitwise counterparts:

  • the bitwise OR operator - C++ and many other programming languages represent this by a single |
  • the bitwise XOR (exclusive or) operator - typically represented by a caret: ^
  • the bitwise AND - typically represented by a single &

The Bitwise OR Operator: |

The bitwise OR operator looks at the binary representation of its two operands and returns a new object of the same type. Every bit in this new object is set to 1 if either of the operands had a 1 in that same position.

It’s easier to demonstrate with an example:

Diagram illustrating the bitwise OR operator

The Bitwise XOR (exclusive or) Operator: ^

XOR works very similarly to OR. The only difference is that, when both operands had a 1 in the same position, the result of XOR will have a 0 in that corresponding position.

Another way to think of XOR is that it will have 1 in each position where the operands had different values.

Here’s an example:

Diagram illustrating the bitwise XOR operator

The Bitwise AND Operator: &

Similarly, the bitwise AND operator looks at the binary representation of its two operands and returns a new object of the same type. Every bit in this new object is set to 1 if both of the operands had a 1 in that same position.

Here’s an example:

Diagram illustrating the bitwise AND operator

Use Case: Bit Flags

Imagine we had a function that creates a window. That function additionally had a collection of settings (or “flags”) for how that window would work. For example, is it resizable? Is it movable? Is it full screen?

Without bitwise operators, there would be two main ways we would create this. We could just have all those booleans be arguments to the function:

CreateWindow("Test Window", false, true, true, false, true);

This is not especially readable. What flags are we setting to be true here? That’s not clear, especially when we don’t have IDE tooltips to help us.

Alternatively, our function could accept a struct instead:

WindowSettings Settings;
Settings.AlwaysOnTop = true;
Settings.Movable = true;
Settings.Closable = true;
CreateWindow("Test Window", Settings);

This makes it clearer what flags we’re setting, but is quite verbose, and may have a performance overhead.

The best approach for this is commonly to use “bit flags”, and bitwise operators can help us implement them.

Using Bitwise Operators for Bit Flags in C++

With bitwise operators, we can make our API much cleaner:

CreateWindow("Test Window", ALWAYS_ON_TOP | MOVABLE | CLOSABLE);

There are many ways to make this work. The standard library comes with a bitset class to solve this problem. Many others use an elaborate solution that involves enums. Here, we’ll do a basic, raw implementation, so we can fully understand what is going on at a binary level.

The first step is to chose a numeric type for our flags. We want our type to have enough bits to cover every possible flag.

Here, we have 5 flags, so an 8 bit unsigned integer like uint8_t will work. This can support 8 bits, but we will only need 5. The first 3 bits will always be 0 in this setup, so we’ll ignore them for clarity in our explanation.

Next, we need to choose a number for each of our flags. We want our numbers to be chosen such that each one has a single bit set to 1, and it is a different bit for each number. For example:

// We include NONE just so we have a descriptive way
// to represent the absence of any flags
static const uint8_t NONE { 0 }; // 00000

// Flags
static const uint8_t RESIZABLE { 1 }; // 00001
static const uint8_t MOVABLE { 2 }; // 00010
static const uint8_t CLOSABLE { 4 }; // 00100
static const uint8_t FULLSCREEN { 8 }; // 01000
static const uint8_t ALWAYS_ON_TOP { 16 }; // 10000

This doubling pattern can continue if we need more flags. If we need more than 8 flags, we’d need to upgrade our type to, for example, a uint16_t

We can now combine our flags in any way we want using the bitwise OR operator. Each possible combination will generate a distinct pattern of bits. In total, there are 32 possibilities, from 0 to 31:

// 00001 | 00010 = 00011 = 3
RESIZABLE | MOVABLE

// 00001 | 00100 = 00101 = 5
RESIZABLE | CLOSABLE

// 10000 | 00001 = 10001 = 17
ALWAYS_ON_TOP | RESIZABLE

// 00010 | 00100 | 01000 = 01110 = 14
MOVABLE | CLOSABLE | FULLSCREEN

// 10001 | 01000 | 00100 | 00010 | 00001 = 11111 = 31
RESIZABLE | MOVABLE | CLOSABLE | FULLSCREEN | ALWAYS_ON_TOP

Checking if a Bit Flag was set in C++

We can now change which flags were set using the bitwise & operator. For example, to check if the CLOSABLE flag was set, we can do this:

void CreateWindow(string Title, uint8_t flags = NONE) {
  if(flags & CLOSABLE) {
    cout << "CLOSABLE was set" << endl;
  }
}

It’s likely to be unclear why this works, but it’s important to understand the logic.

Each of our possible flags will only have one bit containing a 1. In the case of CLOSABLE, it is in the 3rd position: 00100

Therefore, given how the bitwise & works, there are only two possible outcomes when one of the operands is 00100: it will either return 00100 or 00000

It will result in 00100 if the other operand also had a 1 in the 3rd position, ie, it also had CLOSABLE enabled. 00100 is equivalent to the decimal number 4, which is truthy, so the if statement passes.

If the incoming argument did not have a 1 in that position, the CLOSABLE flag was not included. Therefore, flags & CLOSABLE will result in the binary output 00000, which is equivalent to the decimal number 0, which is falsy.

Full Example of C++ Bit Flags using Bitwise Operators

The full code for this example is available below:

#include <iostream>
using std::cout, std::string;

static const uint8_t NONE { 0 }; // 00000
static const uint8_t RESIZABLE { 1 }; // 00001
static const uint8_t MOVABLE { 2 }; // 00010
static const uint8_t CLOSABLE { 4 }; // 00100
static const uint8_t FULLSCREEN { 8 }; // 01000
static const uint8_t ALWAYS_ON_TOP { 16 }; // 10000

void CreateWindow(string Title, uint8_t flags = NONE) {
  cout << Title << " has these flags:";
  if (flags == NONE) {
    cout << " NONE";
    return;
  }
  if (flags & RESIZABLE) {
    cout << " RESIZABLE";
  }
  if (flags & MOVABLE) {
    cout << " MOVABLE";
  }
  if (flags & CLOSABLE) {
    cout << " CLOSABLE";
  }
  if (flags & FULLSCREEN) {
    cout << " FULLSCREEN";
  }
  if (flags & ALWAYS_ON_TOP) {
    cout << " ALWAYS_ON_TOP";
  }
}
 
int main() {
  CreateWindow("Test Window", MOVABLE | CLOSABLE);
}

This program yields the following output:

Test Window has these flags: MOVABLE CLOSABLE

Was this post useful?

3D art showing a progammer setting up a development environment

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ

Not Ready Yet?

Subscribe to this course to return to it later, and get updates on future changes

Get Started Now

Intro to C++ Programming

Starting from the fundamentals, become a C++ software engineer, step by step.

Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved