CppSmart Pointers

Smart Pointers

Smart pointers are objects that wrap a raw pointer and manage its lifetime automatically. Instead of you remembering to call delete at exactly the right place on every code path, a smart pointer's destructor calls it for you the moment the smart pointer itself goes out of scope. This is the direct fix for the leaks, dangling pointers, and double-frees from the previous chapter, and it is built on the same RAII idiom covered later in this section.
What smart pointers solve
With a raw pointer, cleanup depends on every path through your code remembering to call delete exactly once — including early returns, thrown exceptions, and every branch of every if. With a smart pointer, cleanup happens automatically and deterministically as soon as it leaves scope, whether the function returns normally, returns early, or an exception unwinds the stack. You get correct cleanup essentially for free.
The three smart pointer types

Type

Ownership

Copyable?

Typical use

unique_ptr<T>

Exclusive — only one owner at a time

No (move-only)

Default choice for owning a heap object

shared_ptr<T>

Shared — reference-counted, multiple owners

Yes

When multiple parts of the code must jointly own an object

weak_ptr<T>

None — a non-owning observer of a shared_ptr

Yes

Breaking reference cycles, observing without extending lifetime

The general principle: stop calling new/delete directly
Modern, idiomatic C++ code should almost never call raw new or delete directly. Instead, wrap ownership in a smart pointer at the point of allocation, and let the type system and the destructor handle the rest. Later chapters go deep on unique_ptr and shared_ptr/weak_ptr individually — this page is the overview that ties them together.
Preferred creation: make_unique and make_shared
Rather than writing new yourself and handing the result to a smart pointer's constructor, prefer the helper functions std::make_unique<T>() and std::make_shared<T>() (both from <memory>), which construct the object and the owning smart pointer together in one step.

make_unique_shared.cpp

CPP
#include <memory>
#include <iostream>

struct Point {
    int x, y;
    Point(int x, int y) : x(x), y(y) {}
};

int main() {
    auto up = std::make_unique<Point>(3, 4);   // preferred
    auto sp = std::make_shared<Point>(5, 6);   // preferred

    std::cout << up->x << ", " << up->y << std::endl;
    std::cout << sp->x << ", " << sp->y << std::endl;

    // No delete needed anywhere — both are freed automatically
    // when up and sp go out of scope.
    return 0;
}
Prefer make_unique / make_shared over new directly
Writing std::unique_ptr<Point>(new Point(3, 4)) works, but it is considered worse style than std::make_unique<Point>(3, 4). The direct-new form can leak memory in rare cases involving multiple function arguments and exceptions — if one argument's evaluation throws after new already ran but before the smart pointer's constructor takes ownership, that memory can leak. The make_* functions avoid this entirely, and for make_shared specifically, they are also more efficient because they can allocate the object and its reference-count bookkeeping in a single heap allocation.
  • unique_ptr: exclusive ownership, cannot be copied, extremely low overhead — the default choice.

  • shared_ptr: shared ownership via reference counting — use when genuinely multiple owners are needed.

  • weak_ptr: a non-owning reference to a shared_ptr's object — used to avoid reference cycles.

  • Always prefer std::make_unique / std::make_shared over calling new directly.

Coming up
The next two chapters cover unique_ptr and shared_ptr/weak_ptr in depth, including the classic circular reference pitfall and how weak_ptr fixes it.