Skip to content

Pointers, Lifetimes, and Ownership

Memory safety in C++ depends on more than whether a pointer is null. A pointer or reference must designate an object whose lifetime has begun, has not ended, and permits the requested operation.

Storage and object lifetime

Storage provides bytes with alignment. An object's lifetime begins and ends according to construction, destruction, and language rules. Storage can outlive an object, and a pointer value can remain non-null after the designated object's lifetime ends.

int* dangling() {
    int local = 42;
    return &local; // local's lifetime ends on return
}

Dereferencing the result has undefined behavior. The defect is lifetime, not the bit pattern held by the pointer.

Prefer values and scoped owners

std::vector<widget> values;                   // owns widget values
auto owner = std::make_unique<widget>();      // exclusive dynamic owner
widget& alias = *owner;                       // non-owning; owner must outlive use

Dynamic allocation is not required merely because an object is large. Use it when runtime lifetime, polymorphism, stable address, graph structure, or another explicit requirement makes indirection appropriate.

Pointer arithmetic

Pointer arithmetic is defined only within an array object (plus its one-past position) under the language rules. A one-past pointer may be compared or used as an end sentinel but not dereferenced. Prefer iterators, ranges, and std::span to pointer-plus-length pairs.

Ownership contracts

Interface Meaning
T Independent value
T& / T const& Required non-owning alias
T* / T const* Often optional/reseatable non-owning alias
std::unique_ptr<T> Exclusive ownership transfer
std::shared_ptr<T> Shared ownership
std::weak_ptr<T> Non-owning observation of shared ownership
std::span<T> Non-owning contiguous range

Types do not encode every lifetime fact. A reference can still dangle, and returning a span to local storage is invalid.

Invalidation

Container operations can invalidate iterators, pointers, references, and views. For example, vector reallocation invalidates all references into its elements. Read the specific operation's contract; there is no universal “container references remain valid” rule.

Manual allocation

Direct new/delete and new[]/delete[] pairs are error-prone under early return, exceptions, ownership transfer, and partial construction. Encapsulate low-level allocation inside standard containers, smart pointers, allocators, or audited resource types.

Exercises

  1. Identify every lifetime dependency in a class storing std::string_view.
  2. Explain why a non-null pointer may still be invalid.
  3. Refactor a new[]/delete[] buffer to std::vector.