Modern C++ basics

The compiler pipeline, value and reference semantics, and the modern features that replaced preprocessor habits with type-safe ones.

Compiling C++

C++ is compiled and statically typed like C, but the same source only means one thing once you name a language version. Every command below states it explicitly.

g++ -std=c++20 -Wall -Wextra -Wpedantic -g main.cpp -o app
clang++ -std=c++20 -stdlib=libc++ -Wall -g main.cpp -o app   # clang

# several translation units: compile each, then link them together
g++ -std=c++20 -c shapes.cpp -o shapes.o
g++ -std=c++20 -c main.cpp -o main.o
g++ main.o shapes.o -o app
FlagPurpose
-std=c++20Language version: c++17, c++20, c++23
-Wall -WextraThe warnings worth having on from the first line of code
-WerrorRefuse to build until the warnings are fixed
-O2 / -O0Release optimisation / debugging
-gDebug symbols for gdb and the sanitisers
-fsanitize=address,undefinedRuntime memory and undefined-behaviour checks
-IincludeAdd a directory to the header search path
-Llib -lmylibAdd a library directory and link a library

Values, references and const

#include <iostream>
#include <string>
#include <vector>

int main() {
    const std::string name = "Ada";    // immutable, and initialisation is required
    auto count = 3;                    // deduced as int
    auto ratio = 0.75;                 // deduced as double

    std::vector<int> scores{ 90, 85, 77 };

    // pass large objects by const reference: no copy, no accidental modification
    auto total = 0;
    for (const auto &s : scores) total += s;

    // a reference is an alias and cannot be reseated
    int a = 1, b = 2;
    int &ref = a;
    ref = b;                           // assigns 2 into a; a is still a

    std::cout << name << " has " << scores.size() << " scores\n";
    return 0;
}
  • auto deduces from the initialiser; auto& keeps a reference, and const auto& is the usual loop variable.
  • A pointer may be null and may be reassigned; a reference is always bound to an object and cannot be reseated.
  • const is a contract that lets both the compiler and the next reader reason about the code, so prefer it by default.
  • Use std::string and std::vector instead of raw char* and new[]: they know their own size and release themselves.

Replacements for the old habits

Old habitModern replacementWhy
#define MAX 100constexpr int max = 100;Type-checked, scoped and visible to the debugger
NULLnullptrA real pointer type that cannot silently be the integer 0
Manual new and deletestd::unique_ptrReleased exactly once, even when an exception unwinds
An index loop over a vectorA range-based for loopFewer off-by-one bugs, and the intent is visible
enumenum classScoped names with no implicit conversion to int
Copying argumentsconst std::string&No allocation for read-only access
constexpr double kPi = 3.14159265358979;
enum class Colour { red, green, blue };

// structured bindings, C++17
auto [lo, hi] = std::pair{ 1, 9 };

// an if with an initialiser keeps a variable's scope tight
std::map<std::string, int> ages{ { "ada", 36 }, { "grace", 45 } };
if (auto it = ages.find("ada"); it != ages.end()) {
    std::cout << it->first << " is " << it->second << "\n";
}
💡
Turn on -Wall -Wextra with the first line of code you write, and add -Werror in continuous integration. Retro-fitting warnings onto a finished project is far more painful than learning to read them early.

FAQ

Which C++ standard should I target?
C++17 is the safe floor for new code, and C++20 adds concepts, ranges and designated initialisers. Check what your target toolchain actually supports before relying on the newest features.
Is <code>std::vector</code> slower than a C array?
No. Its elements are stored contiguously, exactly like an array, and the size is kept beside the pointer. Any extra indirection usually disappears once the optimiser runs.

Classes, constructors and RAII The standard library and smart pointers

Last refreshed 2026-09-18.