Smart pointers: unique_ptr and shared_ptr
A smart pointer is a small object that owns heap memory and deletes it in its destructor, so you never write delete. Follow a twelve-line program as std::make_unique creates an int with exactly one owner, std::move hands that ownership over, std::make_shared creates an int with a counted control block, copies raise and lower the use count, and at the end of main every owner frees what it owns, in reverse order, with no leak.
5 quick questions
Pick an answer to see why. Your score saves with your progress.
01What does a std::unique_ptr do when it is destroyed?
A unique_ptr owns its object. Its destructor deletes that object, so the memory is freed when the unique_ptr goes out of scope. A unique_ptr that owns nothing (nullptr) deletes nothing.
02Why does auto b = a; fail to compile when a is a std::unique_ptr<int>?
A unique_ptr must be the only owner, so copying is forbidden: its copy constructor is deleted. To hand the object over, move it: auto b = std::move(a);
03After auto b = std::move(a); with a a std::unique_ptr<int>, what does a hold?
Moving a unique_ptr transfers ownership to b and leaves a holding nullptr. The int on the heap does not move or change.
04s is a std::shared_ptr with use count 1. Inside a block you write auto t = s; and then the block ends. What is the use count after the block?
Copying s into t raises the count to 2. When t is destroyed at the end of the block, its destructor lowers the count back to 1. The object stays because s still owns it.
05Two objects hold std::shared_ptrs to each other and nothing else points to them. What happens, and what fixes it?
A reference cycle keeps both use counts at 1 or more forever. A std::weak_ptr observes an object without owning it, so it does not raise the count and breaks the cycle.
Glossary
- Smart pointer
- An object that holds a pointer to heap memory and owns it: its destructor frees the memory. std::unique_ptr and std::shared_ptr are the standard ones, from <memory>.
- Ownership
- Being responsible for deleting an object. Exactly one unique_ptr owns its object; several shared_ptrs can share ownership of one.
- std::unique_ptr
- A smart pointer with exactly one owner. It cannot be copied, only moved; it is usually the same size as a raw pointer.
- std::make_unique / std::make_shared
- Functions that create an object on the heap and return a smart pointer that owns it, so you never write new.
- std::move
- A cast that marks an object as one that may be moved from. Moving a unique_ptr hands its ownership to another and leaves it holding nullptr.
- std::shared_ptr
- A smart pointer that can be copied. All copies share one object and one control block; the object is deleted when the last copy is destroyed.
- Use count
- The number of shared_ptrs that currently own an object, kept in the control block. use_count() reads it.
- std::weak_ptr
- A pointer to an object owned by shared_ptrs that does not own it and does not raise the use count. It breaks reference cycles.
Notes and sources
- The addresses 0x5a10, 0x5a30 and 0x5a40 are made up and short so they fit. Real ones are longer, change from run to run, and the layout of the control block is up to the library.
- std::make_shared usually puts the object and its control block in one allocation; the standard recommends this and the common libraries (libstdc++, libc++, MSVC) do it. std::shared_ptr<int>(new int(5)) needs at least two.
- With the default deleter a std::unique_ptr is usually exactly the size of a raw pointer, and a std::shared_ptr is two pointers (one to the object, one to the control block). The standard does not fix these sizes; they hold on GCC, Clang and MSVC.
- After a unique_ptr is moved from it is guaranteed to hold nullptr. Using *a then would be undefined behaviour, just like any null pointer. For most other standard types a moved-from object is only 'valid but unspecified'.
- The use count is updated in a thread-safe way (typically with atomic instructions), so shared_ptr copies can be made from several threads; that is the small extra cost. use_count() is exact in a single thread like this one, but only approximate while other threads copy the pointer.
- Two objects that hold shared_ptrs to each other keep each other's count above 0 forever: a leak. Making one of the links a std::weak_ptr breaks the cycle (C++ Core Guidelines R.24).
- The program on the card is real, standard C++ (C++14 or later). Build it with g++ -std=c++20 -Wall -Wextra owners.cpp -o owners and run ./owners: it prints 2 and then 1.
- cppreference.com: std::unique_ptr
- cppreference.com: std::unique_ptr constructors (move leaves the source null)
- cppreference.com: std::make_unique
- cppreference.com: std::shared_ptr
- cppreference.com: std::make_shared
- cppreference.com: std::shared_ptr::use_count
- cppreference.com: std::weak_ptr
- cppreference.com: std::move
- cppreference.com: RAII
- C++ Core Guidelines: Smart pointers (R.20 to R.24)
- Standard C++ Foundation (isocpp.org): Freestore management FAQ
Quiz Unlocks after the last step.