The Problem Smart Pointers Solve
On the previous page you allocated memory with new and freed it with delete. That works, but it puts the burden on you: every new needs a matching delete, on every code path, including the ones where an exception is thrown halfway through. Miss one and you leak memory; run delete twice and you corrupt the heap.
Smart pointers fix this by tying the lifetime of heap memory to a normal object on the stack. When that object goes out of scope, its destructor runs delete for you - guaranteed, even if an exception unwinds the stack. This idea is called RAII (Resource Acquisition Is Initialization), and smart pointers live in the <memory> header.
You use *p and p->member exactly as you would a raw pointer. The difference is you never call delete - the smart pointer does it.
unique_ptr: One Owner, No Sharing
unique_ptr is the smart pointer you should reach for by default. It represents exclusive ownership: exactly one unique_ptr owns the object at a time, and when that pointer dies, the object dies with it. It has zero runtime overhead compared to a raw pointer.
Create one with make_unique (C++14). It takes the constructor arguments and hands you a ready-to-use pointer:
Because there can only be one owner, a unique_ptr cannot be copied. Trying to copy it is a compile error - and that error is the language protecting you from two owners both trying to delete the same object:
auto a = make_unique<int>(10);
auto b = a; // error: call to deleted copy constructor of unique_ptr
To hand ownership to someone else, you move it with std::move. After the move, the original pointer is empty (it holds nullptr):
This is the model you want most of the time: there is always exactly one clear owner, and the compiler enforces it.
shared_ptr: Shared Ownership by Reference Count
Sometimes several parts of your program genuinely need to share the same object, and none of them knows which will finish last. That's what shared_ptr is for. It keeps a reference count: each copy bumps the count up, each destruction bumps it down, and the object is freed only when the count hits zero.
Create them with make_shared:
Unlike unique_ptr, copying a shared_ptr is fine - that's the whole point. The trade-off is cost: the reference count is stored on the heap and updated atomically (thread-safe), so shared_ptr is heavier than unique_ptr. Reach for it only when ownership is truly shared, not just to avoid thinking about who owns what.
make_shared is also more efficient than shared_ptr<T>(new T(...)): it allocates the object and the control block in a single allocation instead of two.
weak_ptr and Breaking Reference Cycles
shared_ptr has one classic trap: if two objects hold shared_ptrs to each other, their reference counts never reach zero, so neither is ever freed - a memory leak even though you used smart pointers.
struct Node {
shared_ptr<Node> next; // if two nodes point at each other,
}; // they keep each other alive forever
The fix is weak_ptr: a non-owning observer of a shared_ptr. It does not raise the reference count, so it never keeps an object alive. To use the object, you call .lock(), which gives you a shared_ptr if the object still exists, or an empty one if it's already gone.
Use weak_ptr for "back pointers" and caches - anywhere you want to reference an object without claiming ownership of it.
Common Mistakes and Gotchas
Smart pointers remove most memory bugs, but a few traps remain:
Don't mix smart and raw ownership of the same memory. Never build two smart pointers from the same raw pointer - each will try to delete it:
int* raw = new int(5);
unique_ptr<int> a(raw);
unique_ptr<int> b(raw); // disaster: both will delete the same int (double free)
This is exactly why you prefer make_unique/make_shared - there's no loose raw pointer to misuse.
A unique_ptr is move-only, so pass it by value to transfer ownership. If a function should use but not own the object, take a plain reference or a raw T* instead - a raw pointer that merely observes is perfectly fine:
void consume(unique_ptr<int> p); // takes ownership (move into it)
void observe(int* p); // just looks, owns nothing
Don't reach for shared_ptr by default. It is tempting because it copies freely, but the atomic reference counting costs real performance, and shared ownership makes lifetimes harder to reason about. Default to unique_ptr; upgrade to shared_ptr only when you actually need multiple owners.
unique_ptr for arrays needs the array form. make_unique<int[]>(n) gives you a unique_ptr<int[]> that calls delete[] correctly. In practice, prefer std::vector for dynamic arrays - it manages the memory for you and gives you size tracking on top.
Next: Strings
You now have memory management under control: smart pointers hand you heap allocation without the leaks. One of the most common things you'll allocate and pass around is text - and C++ gives you a much safer tool than raw char* buffers. The next page covers std::string: how it grows on its own, the operations you'll use every day, and why it frees you from manual memory work entirely.
Frequently Asked Questions
What are smart pointers in C++?
Smart pointers are objects from <memory> (unique_ptr, shared_ptr, weak_ptr) that wrap a raw pointer and automatically delete the memory when they go out of scope. They give you heap allocation without the manual delete and the leaks that come with forgetting it.
What is the difference between unique_ptr and shared_ptr?
unique_ptr is the sole owner of its object - it cannot be copied, only moved, and it frees the memory the moment it dies. shared_ptr allows shared ownership through reference counting: many shared_ptrs can point at the same object, and the object is freed only when the last one is destroyed. Prefer unique_ptr unless you genuinely need shared ownership.
Should I use make_unique or new in modern C++?
Use make_unique and make_shared. They allocate and wrap the object in one step, so there is no raw new whose result could leak before it reaches a smart pointer. As a rule of thumb, a modern C++ codebase should have almost no bare new or delete at all.