Tooling, Warnings, and the Feedback Loop
Introduction & Setup

0.4 Tooling, Warnings, and the Feedback Loop

Before learning pointers or optimization, build a fast feedback loop. A compiler, warnings, debugger, tests, and sanitizers catch different classes of mistakes. None replaces careful design, but together they make C++ much easier to learn safely.

Start with a target-based CMake build

Use CMake to describe targets and their requirements rather than copying compiler flags into every command. Keep build artifacts out of the source tree:

cmake_minimum_required(VERSION 3.20)
project(course_app LANGUAGES CXX)

add_executable(course_app main.cpp)
target_compile_features(course_app PRIVATE cxx_std_23)

if (MSVC)
  target_compile_options(course_app PRIVATE /W4 /permissive-)
else()
  target_compile_options(course_app PRIVATE -Wall -Wextra -Wpedantic)
endif()

# Configure once, then build and run tests repeatedly:
# cmake -S . -B build
# cmake --build build
A minimal CMake project with warnings.

Treat warnings as work items. Enable a high warning level from day one, understand each diagnostic, and add -Werror only when the team can keep the configuration portable and clean.

Debug and test before optimizing

Use a debugger to inspect control flow and values; use unit tests for expected behavior; use static analysis for suspicious patterns. For native code, sanitizers are especially valuable because undefined behavior can otherwise look harmless.

# Address + undefined-behavior checks; use a separate build directory
c++ -std=c++23 -g -O1 -Wall -Wextra -Wpedantic \
  -fsanitize=address,undefined main.cpp -o app

# ThreadSanitizer is a separate configuration:
# c++ -std=c++23 -g -O1 -fsanitize=thread main.cpp -o app
A useful GCC or Clang development build.

Sanitizers change performance and do not find every bug, so use them in development and CI. From this point onward, compile every example with warnings enabled, write a small test for each nontrivial function, and measure performance only after correctness is established.

How to verify a hardware claim

When a lesson says one pattern is faster, treat that as a hypothesis until you have evidence on your target machine:

1. Correctness first: unit tests plus AddressSanitizer and UndefinedBehaviorSanitizer catch logic bugs and many lifetime mistakes. They do not measure throughput.

2. Micro-benchmark: run the hot loop many times, warm up once, and compare medians, not a single run. The playground and std::chrono are enough for coarse checks; capstone 10.4 expects documented measurements.

3. Inspect generated code: paste a small loop into Compiler Explorer (the same backend this course playground uses) and confirm whether vectorization or unrolling actually happened.

4. Hardware counters (Linux): perf stat -e cycles,instructions,cache-misses,branch-misses ./your_binary reports where time went. High cache-misses or branch-misses often explain a slowdown better than guessing.

Note

Sanitizers find bugs; perf finds bottlenecks. Use both, but in separate build configurations.

Wire checks into CMake and CI

A program that compiles is not necessarily correct. Use unit tests to lock down behavior, integration tests to verify boundaries such as files and threads, and regression tests whenever a bug is fixed. Turn on useful warnings for your compiler and treat new warnings as review items. clang-tidy catches many maintainability and bug-prone patterns without running the program.

CTest gives a project a standard way to register and run tests. Keep tests as normal executables so the compiler and linker exercise the same target structure used by the application:

cmake_minimum_required(VERSION 3.20)
project(telemetry LANGUAGES CXX)

add_library(metrics src/metrics.cpp)
target_include_directories(metrics PUBLIC include)
target_compile_features(metrics PUBLIC cxx_std_23)

enable_testing()
add_executable(metrics_tests tests/metrics_tests.cpp)
target_link_libraries(metrics_tests PRIVATE metrics)
add_test(NAME metrics_tests COMMAND metrics_tests)
A minimal CMake and CTest setup.

A useful CI pipeline configures, builds, runs tests, and runs sanitizer jobs on every change. Add formatting and static analysis once the project has a consistent baseline.

Suggested route: complete Chapters 0 to 8 and the Capstone (Chapter 10) for the beginner core path. Chapter 9 (concurrency, SIMD, coroutines) is advanced, return after you understand ownership, errors, and pointers. Chapter 11 (C++26 horizon) is experimental reference material.

Chapter 0 Knowledge CheckQuestion 1 of 4

Which practice gives a new C++ project the fastest useful feedback loop before it reaches pointer-heavy code?

Finished reading this lesson?