C++ PROGRAMMING ยท 13 MIN STUDY

Assertions, preconditions, and defensive checks

Use assertions for programmer assumptions and runtime validation for external input.

Learning objectives

  • Explain what an assertion checks.
  • Use assert for internal assumptions during development.
  • Distinguish assertions from input validation.
  • Understand preconditions and postconditions.

Prerequisites: conditions, functions, and basic containers. Review Conditions, Functions, and std::vector first.

What is an assertion?

An assertion checks that a condition expected by the programmer is true at a particular point. The standard assert macro is useful for detecting broken assumptions during development.

C++17
#include <cassert>

int divide_exactly(int numerator, int denominator) {
    assert(denominator != 0);
    return numerator / denominator;
}

If the condition is false and assertions are enabled, the program reports the failed assertion and terminates. Assertions can be disabled when NDEBUG is defined, so they must not be used as the only protection against invalid user input.

Preconditions and postconditions

A precondition is something that must be true before a function is called. A postcondition describes what the function guarantees after it returns successfully. Writing these rules down makes a function's contract clear.

Validate external input separately

For data from a user, file, network, or other external source, use ordinary checks and return an error or report a problem. Such checks must remain active in release builds. An assertion is for a programmer's internal assumption, not for rejecting a user who entered an invalid value.

Common mistakes

  • Using assert to validate a password or user-entered number.
  • Putting a required side effect inside assert, because the expression may not execute when assertions are disabled.
  • Writing assertions that are always true or do not document a meaningful invariant.

Practice

  1. Add an assertion that a vector index is valid in a debug-only helper.
  2. Write ordinary validation for a user-supplied divisor.
  3. Explain why the same validation should not exist only inside assert.
  4. Write one precondition and postcondition for a function that sorts a vector.

Practice solutions

Show worked solutions
  1. #include <cassert>
    #include <cstddef>
    #include <vector>
    int checked_at(const std::vector<int>& values, std::size_t index) {
        assert(index < values.size());
        return values[index];
    }
    This assertion checks an internal precondition in builds where assertions are enabled. Include <cstddef> if naming std::size_t directly.
  2. Check the user-provided divisor with an ordinary if (divisor == 0) and report an input error before performing division.
  3. Defining NDEBUG disables the standard assert macro. User-input validation must still run in release builds, so it must not exist only inside an assertion.
  4. For a sorting function, a precondition could be that the comparator defines a strict weak ordering. A postcondition could be that the vector is sorted according to that comparator and contains the same elements as before.

Check your understanding

Should assert be used to reject invalid user input?

Answer: No. Use runtime validation for external input because assertions may be disabled in release builds.

Summary

Assertions document and check programmer assumptions. Preconditions and postconditions define contracts. Validate untrusted input with ordinary runtime logic that remains active in production.

References