C++ / a working model

38 / 163   ·   C++14   ·   9 min

Smart Pointers: Choosing an Ownership Vocabulary

Keep this sentence

Prefer direct value members and containers first. When a dynamic lifetime is truly needed, default to unique_ptr for exclusive ownership, and use shared_ptr only when multiple participants must all extend the lifetime. Observers use references, raw pointers, or weak_ptr; do not upgrade every access into ownership.

In this lesson
  1. Choose Value Semantics Before Dynamic Allocation
  2. An Accessor Need Not Become an Owner
  3. Consider Factories Together with Deleters
  4. Example
  5. Exercise

Choose Value Semantics Before Dynamic Allocation

If a class always owns a simple member, storing that member by value is usually the clearest choice. For a dynamic array prefer vector rather than allocating a pointer for each element. Only when you need polymorphism, a stable independent object location, an optional dynamic resource, or transfer across scopes should you go on to consider smart pointers.

unique_ptr means there is only one owner at a given moment; it cannot be copied but can be moved. shared_ptr means multiple owners jointly decide when the object is destroyed, and a copy joins the same ownership group. The choice between them is decided by the business lifetime; do not treat shared_ptr as universal insurance that removes the duty to analyze ownership.

An Accessor Need Not Become an Owner

A function that briefly reads an object can take const T&; an optional observer can use T*. These borrows do not extend lifetime; the caller must guarantee the target is still alive. When you need to observe a shared object without preventing reclamation, use weak_ptr and, at the actual access, obtain temporary ownership through lock.

The example moves a unique_ptr into a vector; the source pointer becomes empty and the vector takes over destruction. A raw pointer obtained before the insert still points at the same dynamic object, because what moved is the unique_ptr, not the integer it manages. After an element is erased or the owner is reset, however, the observer pointer is immediately invalid.

Consider Factories Together with Deleters

Prefer make_unique and make_shared so that creation and management become one complete operation. When cooperating with a C interface, you must provide a deleter that matches the resource’s release protocol; storage obtained from malloc needs free and must not be given to a unique_ptr that uses delete by default. The deleter itself is part of the ownership type’s contract.

unique_ptr<T[]> can manage a new[] array but does not record length, so it cannot replace a vector that carries size information. When a derived object is deleted through a base unique_ptr, the base generally needs a suitable virtual destructor. A smart pointer can automatically execute the correct policy; it cannot turn an incorrect destruction policy into a correct one.

Pitfalls

  • get only provides a borrowed address; do not hand it to another independent owning smart pointer, or you may double-release.
  • The size of unique_ptr depends on the deleter type; do not treat “always equal to a raw pointer” as a portable interface guarantee.

Run an example

Minimum C++14 · complete program · Download .cpp

#include <cassert>
#include <memory>
#include <utility>
#include <vector>

int main() {
    auto item = std::make_unique<int>(7);
    int* observer = item.get();
    std::vector<std::unique_ptr<int>> items;
    items.push_back(std::move(item));
    assert(!item);
    assert(items.front().get() == observer);
    assert(*observer == 7);
    *items.front() = 9;
    assert(*observer == 9);
}

Compile locally

g++ -std=c++14 -Wall -Wextra -Wpedantic -pthread memory-smart-pointers.cpp -o example && ./example

Expected result

Expected: exit 0, no output; every assert holds.

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

In a tree, a parent exclusively owns its children, and a child only needs to access its parent. How should the two directions of the relationship be expressed?

Show a reference answer

The parent can own children with vector<unique_ptr<Node>>, and a child can non-owningly observe the parent with Node* parent. Constrain children so they cannot outlive the parent independently, and update parent on reattachment. Only if the design allows children to share lifetime independently should you remodel, for example sharing ownership of children and observing a shared parent with weak_ptr, rather than mechanically changing every edge to shared_ptr.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog