C++

C++ behaviour depends on object lifetime, linkage and the rules the optimiser can assume. These notes cover common sources of mistakes; use the standard for precise details.

Linkage, and why the ODR matters

Internal linkage keeps a name within one translation unit. External linkage allows it to refer to the same entity across translation units. Unnamed namespaces are useful for file-local definitions.

The one-definition rule limits how definitions can be repeated. Inline definitions may appear in multiple translation units if they meet its requirements. Violations can go undiagnosed, including when macros give different definitions in different files.

extern "C" controls language linkage. It does not make a definition file-local.

Templates: when names get bound

Non-dependent names are looked up when a template is defined. Dependent names also involve rules applied during instantiation, including argument-dependent lookup.

An unqualified swap after using std::swap; allows suitable overloads to be found through argument-dependent lookup. typename and template disambiguate certain dependent names.

Value categories, elision, and lifetime

Value categories affect binding and moving. Since C++17, specified prvalue cases construct the result directly. Named return value optimisation remains optional.

Storage and a live object are different things. A cast alone does not establish that an object exists with the required type and lifetime. Reusing storage and accessing it through another type need care.

Use supported operations such as std::bit_cast or memcpy for suitable representation conversions rather than assuming a pointer cast permits them.

The memory model

Atomics specify both indivisible access and memory ordering. Sequential consistency provides a total order for the relevant operations. Release and acquire can publish data between threads; relaxed operations do not order unrelated data.

Unsynchronised conflicting non-atomic accesses are data races and cause undefined behaviour. See Concurrency.

Exceptions and noexcept

noexcept affects how library code chooses between moving and copying. If an exception escapes a noexcept function, the program terminates.

Disabling exceptions or RTTI changes which language and library features you can use. Check the project's requirements before doing so.

Constant evaluation

constexpr allows constant evaluation when its requirements are met. consteval requires a call to be evaluated at compile time. constinit requires static initialisation without making the object constant.

Avoid dependencies on dynamic initialisation order across translation units.

Allocation

std::pmr lets containers use memory resources without fixing the resource in their type. A monotonic resource can suit work discarded together, but its storage must outlive all objects using it.

Measure allocation cost and contention before replacing an allocator.

Undefined behaviour

The optimiser may assume undefined behaviour never occurs. This can remove checks or change code in ways that are hard to understand from the source alone. Use sanitizers and small reproductions to investigate.

Ownership and interfaces

Use RAII to tie resource cleanup to object lifetime. Prefer values and standard containers for ordinary ownership. Use std::unique_ptr for exclusive dynamic ownership; use std::shared_ptr when ownership genuinely has to be shared.

A raw pointer or reference does not establish ownership. Make the lifetime requirement clear at an interface boundary. Views such as std::span and std::string_view refer to storage owned elsewhere.

Iterator invalidation

Container operations can invalidate pointers, references and iterators. Reallocation in a vector is a common cause. Check the container's rules before keeping a view across insertion or erasure.

Moving an object does not promise that every pointer into it remains valid. A moved-from object is still subject to its type's documented guarantees.

Build and diagnose

Keep warning settings and the selected language standard in the build configuration. Diagnose a compilation error, a linking error and a runtime error at their respective stages.

Check headers for missing direct includes; a build should not depend on another header incidentally including what you use. Run appropriate sanitizers on representative paths, and retain a small reproducer for undefined behaviour or compiler issues.

Reading