Smart Pointers
Smart pointers are objects that own a heap-allocated object and delete it automatically when they're done with it. std::unique_ptr expresses single ownership, std::shared_ptr shared ownership, and std::weak_ptr a non-owning observer of a shared object. With them, you never write delete.
std::unique_ptr
A unique_ptr is the sole owner of its object. When the unique_ptr is destroyed — at the end of its scope, or when its owner is destroyed — it deletes the object:
#include <iostream> #include <memory> #include <string> struct Engine { std::string model; explicit Engine(std::string m) : model(std::move(m)) { std::cout << "start " << model << '\n'; } ~Engine() { std::cout << "stop " << model << '\n'; } }; int main() { auto e = std::make_unique<Engine>("V8"); // create std::cout << "using " << e->model << '\n'; // use like a pointer { auto small = std::make_unique<Engine>("electric"); } // deleted here std::cout << "end of main\n"; } // V8 deleted here
start V8 using V8 start electric stop electric end of main stop V8
Create them with std::make_unique, never with a raw new. A unique_ptr costs nothing extra compared with a raw pointer.
Moving ownership
A unique_ptr can't be copied — there can only be one owner — but ownership can be moved:
#include <iostream> #include <memory> #include <utility> #include <vector> int main() { auto a = std::make_unique<int>(42); // auto b = a; // error: can't copy a unique_ptr auto b = std::move(a); // ownership moves to b std::cout << std::boolalpha << (a == nullptr) << ' ' << *b << '\n'; std::vector<std::unique_ptr<int>> owners; owners.push_back(std::move(b)); owners.push_back(std::make_unique<int>(7)); for (const auto& p : owners) std::cout << *p << ' '; std::cout << '\n'; }
true 42 42 7
Functions express ownership in their signatures: a parameter of type std::unique_ptr<T> takes ownership; returning one gives ownership to the caller. Functions that only use the object take T& or T*.
Polymorphic objects
The most common reason to heap-allocate in modern C++ is storing objects of different classes behind a base-class pointer (lesson 37):
#include <iostream> #include <memory> #include <vector> struct Shape { virtual ~Shape() = default; virtual double area() const = 0; }; struct Square : Shape { double side; explicit Square(double s) : side(s) {} double area() const override { return side * side; } }; struct Circle : Shape { double r; explicit Circle(double radius) : r(radius) {} double area() const override { return 3.14159 * r * r; } }; int main() { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Square>(2.0)); shapes.push_back(std::make_unique<Circle>(1.0)); for (const auto& s : shapes) std::cout << s->area() << '\n'; }
4 3.14159
std::shared_ptr
When several parts of a program need to keep the same object alive, use shared_ptr. It keeps a reference count; the object is deleted when the last shared_ptr to it goes away:
#include <iostream> #include <memory> #include <string> struct Texture { std::string file; explicit Texture(std::string f) : file(std::move(f)) {} ~Texture() { std::cout << "unloading " << file << '\n'; } }; int main() { auto grass = std::make_shared<Texture>("grass.png"); std::cout << "owners: " << grass.use_count() << '\n'; { auto tile1 = grass; // copying adds an owner auto tile2 = grass; std::cout << "owners: " << grass.use_count() << '\n'; } std::cout << "owners: " << grass.use_count() << '\n'; grass.reset(); // last owner gone: deleted now std::cout << "done\n"; }
owners: 1 owners: 3 owners: 1 unloading grass.png done
Shared ownership has costs — atomic reference counting and harder-to-follow lifetimes — so use unique_ptr unless ownership really is shared.
std::weak_ptr and cycles
If two objects hold shared_ptrs to each other, their counts never reach zero and both leak. A weak_ptr observes a shared object without owning it; you lock() it to get a temporary shared_ptr, which is empty if the object has already gone:
#include <iostream> #include <memory> int main() { std::weak_ptr<int> observer; { auto data = std::make_shared<int>(99); observer = data; if (auto p = observer.lock()) std::cout << "still alive: " << *p << '\n'; } if (observer.expired()) std::cout << "object is gone\n"; }
still alive: 99 object is gone
Typical uses: back-pointers from child to parent, caches, and observers.
Guidelines
- Prefer objects by value and containers; heap-allocate only when you need to.
- Use
std::make_uniqueby default,std::make_sharedwhen ownership is genuinely shared. - Pass
T&,const T&orT*to functions that just use an object — not the smart pointer. - Raw pointers never own.
Try it yourself
Build a small playlist that owns its songs through unique_ptrs, and move a song from one playlist to another.
morning: 1 song(s), evening: Coffee Time