Smart Pointers in C++: unique_ptr, shared_ptr and weak_ptr with Worked Examples
Learn who owns a C++ object, when it is destroyed, and how to choose among unique_ptr, shared_ptr, weak_ptr and a borrowed raw pointer.
KnowledgeGate Team
Exam prep & CS education

A raw pointer can point to an object without saying who must delete it. Early returns, exceptions and ownership hand-offs then make simple code fragile in practice. std::unique_ptr provides exclusive ownership, std::shared_ptr provides shared ownership, and std::weak_ptr provides non-owning observation. Smart pointers manage the lifetime of dynamically allocated objects. They are not replacements for every pointer in a program.
Smart pointers in C++: ownership, lifetime and RAII
A pointer stores an address. Ownership is the responsibility to release a resource. Lifetime is the interval during which the object exists and may be used.
RAII binds a resource's lifetime to an object's lifetime. When an owning smart-pointer object is destroyed, it releases its owned object.
Widget *raw = new Widget(42);
if (!valid) return -1; // leak: delete is skipped
delete raw;Compare that with:
auto owned = std::make_unique<Widget>(42);
if (!valid) return -1; // owned is destroyed during returnThe second version deletes Widget(42) exactly once even on the early return. A stack address or borrowed API parameter usually needs a reference or raw pointer. The Coding & DSA Courses for Placements page provides the wider C++ learning path. C++ ownership is also distinct from operating-system memory topics such as Memory Management in OS: Paging and Segmentation. Smart pointers do not control pages or virtual addresses.
unique_ptr in C++: exclusive ownership
std::unique_ptr<T> has one owner. It cannot be copied, but ownership can be transferred by a move operation. Prefer std::make_unique<T>(arguments) for ordinary construction.
#include <iostream>
#include <memory>
#include <utility>
struct Book {
int id;
explicit Book(int value) : id(value) {}
~Book() { std::cout << "destroy " << id << '\n'; }
};
int main() {
auto first = std::make_unique<Book>(42);
auto second = std::move(first);
std::cout << std::boolalpha << static_cast<bool>(first) << '\n';
std::cout << second->id << '\n';
}The exact output is:
false
42
destroy 42Before the move, first owns Book(42). std::move(first) enables its transfer. Afterwards, first == nullptr and second owns the object. At the end of main, second deletes the book once. auto copy = first; would not compile because exclusive ownership cannot be copied.
For a dynamic array, auto values = std::make_unique<int[]>(4); allocates four integers. Assigning values[0] through values[3] the values 3, 5, 8, 13 makes values[3] equal to 13. Array deletion is automatic.

shared_ptr in C++: reference counting
Copied std::shared_ptr<T> objects share ownership through one control block. The object is destroyed when the last strong owner releases it. Prefer std::make_shared<T>(arguments).
auto owner = std::make_shared<int>(7); // count 1
auto copy = owner; // count 2
{
auto temporary = copy; // count 3
} // count 2
copy.reset(); // count 1
owner.reset(); // count 0, destroy int(7)State | Strong count |
|---|---|
after | 1 |
after copy | 2 |
inside inner scope | 3 |
after inner scope | 2 |
after | 1 |
after | 0 |
The integer remains 7 throughout its lifetime and is destroyed only at the transition from 1 to 0. use_count() can illustrate this trace, but should not drive concurrent logic because the count can change. A shared_ptr carries control-block bookkeeping. Safe count updates do not make unsynchronised writes to the object safe.
weak_ptr in C++: observing and breaking cycles
Suppose external shared_ptr owners hold Person("Alice") and Person("Bob"). If each person's partner is a shared_ptr to the other, both strong counts become 2. Resetting the external owners leaves each count stuck at 1 through the cycle, so neither destructor runs.
Declare std::weak_ptr<Person> partner; instead. Assigning Bob as Alice's partner and Alice as Bob's partner leaves both strong counts at 1 because weak references do not own.
{
auto locked = alice->partner.lock();
if (locked) std::cout << locked->name; // Bob
}Inside the scope, locked raises Bob's strong count from 1 to 2. It returns to 1 when locked is destroyed. If Bob has expired, lock() returns an empty shared_ptr, so check it before dereferencing. Use weak_ptr for observers, caches and back-references when the target is already managed by shared_ptr, not for every raw observer.

Choosing a smart pointer or raw observer
Need | Type | Ownership rule | Copy or move | Concrete example |
|---|---|---|---|---|
One dynamic owner |
| Exclusive | Move only | Factory returns |
Genuine co-owners |
| Shared strong ownership | Copy allowed | Components share |
Observe shared state |
| Does not own | Copy allowed | Cache watches that session |
Borrow during a call |
| Does not own | No ownership transfer | Inspector, with pointer if null is meaningful |
Begin with unique_ptr. Choose shared_ptr only when several independent owners are required, then use weak_ptr for non-owning edges in that shared graph. Function signatures make the intent visible: std::unique_ptr<Book> make_book() returns ownership, void consume(std::unique_ptr<Book> book) takes it, and void inspect(const Book& book) borrows without extending lifetime.
Smart-pointer mistakes and repairs
Mistake | What goes wrong | Repair | Why |
|---|---|---|---|
| Two control blocks try to delete one address |
| One control block, count 2 |
|
| Use it only while |
|
Discard | The object leaks | Let |
|
Strong back-references | A cycle keeps counts above 0 | Make the back-reference weak | The cycle no longer owns itself |
Using shared_ptr by habit can also hide the intended owner. Draw the ownership graph and default to unique_ptr. If an object already under shared ownership must safely produce a shared_ptr to itself, use enable_shared_from_this; never construct a fresh shared_ptr(this).
Smart pointers in interviews and code traces
After
auto p = std::make_unique<int>(12); auto q = std::move(p);,pis empty and*qis12.After
auto a = std::make_shared<int>(30); auto b = a; std::weak_ptr<int> w = a;, the strong count is 2, not 3.After
b.reset(), the strong count is 1 andw.expired()is false whileastill owns the integer.
Practise classifying ownership, predicting compilation failures, tracing strong counts, finding cycles and deciding whether a destructor runs. Keep the core distinction exact: unique_ptr means exclusive ownership, shared_ptr means shared ownership, and weak_ptr means non-owning observation of shared state. Once object lifetime is clear, Time Complexity and Asymptotic Notation is a useful next programming-analysis lesson.
Smart pointers in C++: the short version
Identify the owner.
Prefer automatic objects where possible.
Default dynamic ownership to
unique_ptr.Use
shared_ptronly for real co-ownership.Use
weak_ptrto observe shared state and break cycles.Never create two smart owners independently from one raw address.
Moving Book(42) leaves first empty. The shared int(7) is destroyed at the 1-to-0 strong-count transition. Continue with the C++ Programming Course for a sequenced route through C++ concepts and coding practice. Then compile the book example, add one temporary owner to the integer trace, predict every state first, and confirm the output.
Keep learning

Functions in C: Syntax, Parameters and Worked Examples
Learn how C declarations, calls, and definitions fit together. Compile two complete programs, follow their values, and trace composition and recursion.

Pointers in C: Addresses, Arrays and Functions with Worked Examples
Build a practical pointer model with runnable C programs for dereferencing, arrays, swapping, dynamic memory and common tracing questions.

Pointers and Arrays in C: Decay, Indexing and Worked Examples
Separate arrays from pointers with runnable traces covering indexing, one-past boundaries, function parameters, two-dimensional arrays and declaration traps.

Function Pointers in C: Declarations, Callbacks and Runnable Examples
Learn to read function-pointer declarations, build a callback dispatch table, guard invalid selections, and write a safe qsort comparator in C.