thecodex.expert · The Codex Family of Knowledge
C++

Undefined Behaviour

Calling push_back on a vector can silently invalidate every iterator into it — continuing to use one afterward compiles perfectly and is genuine undefined behavior.

C++20 / C++23 Iterator invalidation is a real, common trap Last verified:
Canonical Definition

C++ inherits C's full range of undefined behavior (out-of-bounds access, use-after-free, signed integer overflow, uninitialized reads) since it shares C's low-level memory model, and adds distinctly C++-flavored categories on top. Iterator invalidation is a particularly common one: many container operations (a vector reallocating on push_back, erasing an element from a map) silently invalidate existing iterators, and continuing to use one afterward is undefined behavior with no compiler warning by default. Calling a virtual function from a constructor or destructor is another genuine C++-specific trap: it does NOT dispatch to the most-derived override, since the derived part of the object isn't fully constructed (or has already been destructed) at that point — a subtlety that surprises many people coming from languages without this restriction.

Iterator invalidation: a genuine, common trap

push_back MIGHT trigger a reallocation if the vector's current capacity is exhausted, moving all its elements to a new memory block — it is entirely valid content-wise, but it, first, and every other existing iterator into v are now dangling.

C++iterator_invalidation.cpp
std::vector<int> v = {1, 2, 3};
auto it = v.begin();

v.push_back(4);   // MIGHT reallocate — if it does, it is now DANGLING

// std::cout << *it;   // UNDEFINED BEHAVIOR if a reallocation happened — compiles fine either way

// safer: re-obtain the iterator after any modification that might invalidate it
it = v.begin();
std::cout << *it;   // safe

Virtual functions in constructors/destructors don't dispatch as expected

Calling a virtual function inside Base's constructor calls Base's OWN version, never Derived's override — even if the object being constructed is genuinely a Derived — because the Derived portion of the object hasn't been constructed yet at that point, so treating it as a Derived would be unsafe.

C++virtual_in_constructor.cpp
class Base {
public:
    Base() {
        init();   // calls Base::init(), NOT Derived::init() — even for a Derived object!
    }
    virtual void init() { std::cout << "Base::init\n"; }
};

class Derived : public Base {
public:
    void init() override { std::cout << "Derived::init\n"; }
};

Derived d;   // prints "Base::init" — surprising, but NOT undefined behavior, just non-obvious dispatch

Signed integer overflow: still genuinely undefined

Unlike unsigned overflow (which wraps around predictably, defined by the standard), signed overflow is undefined behavior — the compiler is permitted to assume it never happens, which has led to real, surprising optimizer behavior in code that relied on it silently wrapping the way C's or Java's signed overflow does.

C++signed_overflow.cpp
int max = std::numeric_limits<int>::max();
int overflowed = max + 1;   // UNDEFINED BEHAVIOR — not a defined wraparound, unlike unsigned

Sources

1
ISO/IEC 14882 (C++ Standard), "Undefined behavior," cppreference.com/w/cpp/language/ub.