C++ Assertions: assert and static_assert

Learn how we can ensure that our C++ application is in a correct state using run time and compile time assertions.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

4a.jpg
Ryan McCombe
Ryan McCombe
Posted

Whenever we write a function that accepts some arguments, we generally have some assumptions about those arguments. For example, if we have a function that takes a pointer, we might assume that pointer is not a nullptr.

These are sometimes called “preconditions” - things that our function requires to be true before it can do its task.

In the beginner courses, we recommended checking for preconditions using an if statement:

#include <iostream>

class Character {
public:
  std::string GetName() {
    return "Legolas";
  };
};

void LogName(Character* Player) {
  if (!Player) return;
  std::cout << Player->GetName();
}

int main() {
  LogName(nullptr);
}

This works in tiny programs, but the approach has a few problems:

  • It obfuscates what the preconditions are. Is an if statement documenting a precondition, or is it just handling something that can happen when our program is running normally?
  • It can hide bugs, or move the bug elsewhere. If one of our preconditions isn’t true, we’d rather know about it when developing our software so we can address the bug. Additionally, having the bug’s impact be as close as possible to where it first showed up makes debugging easier.
  • Not every failed precondition can be recovered from in the function where the error shows up. We want more control over how to deal with these types of errors.
  • An if statement has a runtime performance cost.

Using assert()

Instead of using an if statement, we can instead use the assert() macro. This is available after including cassert:

#include <cassert>
void LogName(Character* Player) {
  assert(Player);
  std::cout << Player->GetName();
}

When running in release mode, the assert calls are stripped out of the code, so it has no performance cost. However, if we build and run our program in debug mode, it will now crash as expected.

Reviewing our crash dump, we can now see why, which will help us debug:

main.cpp:12: void LogName(Character *):
Assertion `Player' failed.

Assertions with Custom Error Messages

We can pass any boolean expression into assert. If it is not true, our program will terminate:

int Divide(int x, int y) {
  assert(y != 0);
  return x/y;
}

However, in the crash dump, the error messages can be quite cryptic. When our program gets large, it can be quite difficult to understand what went wrong when an assertion like this failed:

main.cpp:17: int Divide(int, int):
Assertion `y != 0' failed

It can be helpful to give our asserts more descriptive text to explain what exactly the problem is. That can be done in a few ways:

int Divide(int x, int y) {
  assert(("Cannot divide by zero", y != 0));
  return x/y;
}
main.cpp:17: int Divide(int, int):
Assertion `("Cannot divide by zero", y != 0)' failed.
int Divide(int x, int y) {
  assert(y != 0 && "Cannot divide by zero");
  return x/y;
}
main.cpp:17: int Divide(int, int):
Assertion `y != 0 && "Cannot divide by zero"' failed.

In either case, neither the syntax to create the assertion, nor the output it generates, are perfect.

Because of this, developers tend to implement their own assert macros, or use one provided by a third-party library like Boost.Assert

As a result, the specific implementation of assertions may change from one code base to the next.

However, the syntax can be learned very quickly. Understanding the benefits of assertions, and where to use them, is the knowledge that endures.

Compile Time Assertions with static_assert

We can also make assertions at compile time, using static_assert. Static assert does not require any headers to be included - it is part of the language by default.

Naturally, static_assert can only be used to assert things that are static, ie, known at compile time:

class Character {
public:
  static const int MaxHealth { -100 };
};
static_assert(Character::MaxHealth > 0);

This code yields a compilation error similar to:

main.cpp:10:36: error: static assertion failed

We can provide a string as the second argument to static_assert, giving us the ability to add custom error messages.

Unlike the standard library’s implementation of assert, static_assert has a much cleaner syntax and output:

static_assert(
  Character::MaxHealth > 0,
  "Characters must have a positive value for their max health"
);
main.cpp:10:36: error: static assertion failed:
Characters must have a positive value for their max health

Asserting Type Traits with static_assert

Typically, static_assert is used to investigate types. For example, we might want to ensure that our data types have enough memory on the system the code is being compiled on:

// Ensure integers have at least 4 bytes of memory
static_assert(size(int) >= 4);

Static assertions are also commonly used when dealing with templated code, in conjunction with the type_traits header:

#include <type_traits>

template<typename T>
T Square(T x) {
  static_assert(
    std::is_arithmetic<T>::value,
    "This function only works with arithmetic types"
  );
  return x * x;
}

Newer features of the language (such as concepts) are now dedicated to solving these types of problems. However, static assertions are still commonly used in existing code bases.

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Exceptions and Error Handling
7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

throw, try and catch in C++

Learn how we can use the throw, try and catch statements to let our program manage unexpected situations
52.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved