Question
What Are Smart Pointers in C++ and When Should You Use Them?
Question
What is a smart pointer in C++, and when should it be used?
I want to understand what smart pointers are, how they differ from raw pointers, and in which situations I should use them in real C++ programs.
Short Answer
By the end of this page, you will understand what smart pointers are in C++, why they help prevent memory-management bugs, and when to choose std::unique_ptr, std::shared_ptr, or std::weak_ptr. You will also see practical examples, common mistakes, and a small project that shows how automatic resource management works.
Concept
Smart pointers are C++ objects that act like pointers but also manage the lifetime of dynamically allocated memory automatically.
In older C++ code, developers often used raw pointers with new and delete:
int* p = new int(42);
delete p;
This works, but it is easy to make mistakes:
- forgetting to call
delete - calling
deletetwice - using a pointer after the object has already been deleted
- leaking memory when exceptions occur
Smart pointers solve this by tying ownership of a resource to an object whose destructor performs cleanup automatically. This idea follows RAII: Resource Acquisition Is Initialization.
The main standard smart pointers in C++ are:
std::unique_ptr— one ownerstd::shared_ptr— multiple shared ownersstd::weak_ptr— non-owning reference to an object managed byshared_ptr
Why this matters in real programming:
- it makes memory management safer
- it reduces leaks
- it makes ownership clearer in code
- it works correctly even when exceptions happen
- it improves maintainability in larger codebases
In modern C++, smart pointers should usually be preferred over manual new and delete.
Mental Model
Think of a smart pointer like a keycard system for a room:
- A raw pointer is like writing the room number on paper. You know where the room is, but nobody is responsible for locking it or cleaning it up.
- A
unique_ptris like one person holding the only keycard. When that person leaves, the room is closed properly. - A
shared_ptris like several people sharing access. The room stays open until the last person leaves. - A
weak_ptris like a visitor pass that lets you check whether the room still exists, but it does not keep the room open.
The key idea is ownership. Smart pointers are not just about pointing to something. They answer the question: who is responsible for cleaning this up?
Syntax and Examples
Core syntax
std::unique_ptr
#include <memory>
std::unique_ptr<int> p = std::make_unique<int>(42);
- Owns one object exclusively
- Cannot be copied
- Can be moved
- Deletes the object automatically when it goes out of scope
std::shared_ptr
#include <memory>
std::shared_ptr<int> p = std::make_shared<int>(42);
std::shared_ptr<int> q = p;
- Multiple smart pointers can share ownership
- The object is deleted only when the last
shared_ptris destroyed
std::weak_ptr
#include <memory>
std::shared_ptr<int> p = std::make_shared<int>(42);
std::weak_ptr<int> w = p;
- Does not own the object
- Used to observe an object managed by
shared_ptr - Helps avoid circular ownership problems
Beginner-friendly example
#include <iostream>
#include <memory>
class File {
public:
File() {
std::cout << "File opened\n";
}
~File() {
std::cout << "File closed\n";
}
void read() {
std::cout << "Reading file\n";
}
};
int main() {
std::unique_ptr<File> file = std::make_unique<File>();
file->read();
return 0;
}
What this shows
fileowns theFileobject- You use
->just like with a normal pointer - When
mainends,fileis destroyed - Its destructor automatically deletes the
Fileobject - The
Filedestructor runs without needingdelete
This is safer than manually writing new File() and delete.
Step by Step Execution
Consider this example:
#include <iostream>
#include <memory>
class Item {
public:
Item() {
std::cout << "Item created\n";
}
~Item() {
std::cout << "Item destroyed\n";
}
};
int main() {
{
std::unique_ptr<Item> ptr = std::make_unique<Item>();
std::cout << "Using item\n";
}
std::cout << "Block ended\n";
}
Step by step:
-
std::make_unique<Item>()creates anItemobject. -
That object is owned by
ptr. -
The constructor prints:
Item created -
The next line prints:
Using item -
Execution reaches the end of the inner block
{ ... }. -
ptrgoes out of scope. -
Because
ptris aunique_ptr, its destructor deletes theItemautomatically. -
The
Itemdestructor prints:Item destroyed -
Then the program continues and prints:
Block ended
Expected output:
Item created
Using item
Item destroyed
Block ended
This is the main benefit of smart pointers: cleanup happens automatically when ownership ends.
Real World Use Cases
Smart pointers are used whenever a program needs clear, safe ownership of dynamically allocated objects.
Common use cases
Managing objects created at runtime
auto user = std::make_unique<User>();
Useful when an object should exist only while a scope or owner exists.
Returning heap-allocated objects from functions
std::unique_ptr<Connection> createConnection() {
return std::make_unique<Connection>();
}
This makes ownership explicit: the caller receives the connection and owns it.
Storing polymorphic objects
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>());
shapes.push_back(std::make_unique<Rectangle>());
This is very common in C++ because base-class objects often need pointer-based storage.
Shared resources across parts of an application
auto config = std::make_shared<Config>();
serviceA.setConfig(config);
serviceB.setConfig(config);
Use this when several parts of the program truly need shared ownership.
Caches, observers, and graph-like structures
std::weak_ptr is often used when one object needs to reference another without extending its lifetime.
Example:
- a UI widget observing a shared model
- a cache storing references without forcing objects to stay alive
- parent/child relationships that would otherwise create cycles
Real Codebase Usage
In real projects, smart pointers are mostly about expressing ownership clearly.
Common patterns
Prefer unique_ptr by default
Many codebases follow this rule:
- use stack objects when possible
- if dynamic allocation is needed, use
std::unique_ptr - use
std::shared_ptronly when shared ownership is truly required
Factory functions
std::unique_ptr<Logger> createLogger() {
return std::make_unique<Logger>();
}
This makes ownership transfer obvious.
Polymorphism in containers
std::vector<std::unique_ptr<Plugin>> plugins;
plugins.push_back(std::make_unique<AudioPlugin>());
This avoids slicing and ensures cleanup happens automatically.
Guarding optional resources
class Service {
private:
std::unique_ptr<Connection> connection;
};
If connection is present, it is owned by the service. If not, it can be nullptr.
Shared application state
auto session = std::make_shared<Session>();
controller.setSession(session);
view.setSession(session);
This is common in systems where several objects depend on the same long-lived resource.
Breaking cycles with weak_ptr
If two objects own each other with shared_ptr, they may never be deleted. One side is usually changed to weak_ptr.
Practical advice
- Prefer
std::make_uniqueandstd::make_shared - Avoid naked
newin application code - Pass raw pointers or references when you only need access, not ownership
- Use smart pointers to model ownership, not just to replace every pointer blindly
Common Mistakes
1. Using shared_ptr when unique_ptr is enough
Beginners often choose shared_ptr because it feels more flexible.
std::shared_ptr<int> p = std::make_shared<int>(10);
This is not always wrong, but it adds reference counting and weaker ownership clarity.
Better:
std::unique_ptr<int> p = std::make_unique<int>(10);
Use shared_ptr only when multiple owners are really needed.
2. Mixing raw new and manual delete with smart pointers
Broken example:
std::unique_ptr<int> p(new int(5));
delete p.get();
This is wrong because unique_ptr will later try to delete the same memory again.
Avoid this by letting the smart pointer handle deletion.
Correct:
auto p = std::make_unique<int>(5);
3. Creating multiple shared_ptrs from the same raw pointer
Broken example:
int* raw = new int(7);
std::shared_ptr<int> a(raw);
std::shared_ptr<int> b(raw);
This creates two separate owners that both think they should delete the same object.
Correct:
auto a = std::make_shared<int>(7);
std::shared_ptr<int> b = a;
4. Forgetting that unique_ptr cannot be copied
Broken example:
std::unique_ptr<int> a = std::make_unique<int>(1);
std::unique_ptr<int> b = a;
This fails because unique_ptr has exclusive ownership.
Correct:
std::unique_ptr<int> a = std::make_unique<int>(1);
std::unique_ptr<int> b = std::move(a);
After the move, a no longer owns the object.
5. Causing circular references with shared_ptr
Broken pattern:
struct B;
struct A {
std::shared_ptr<B> b;
};
struct B {
std::shared_ptr<A> a;
};
If A and B point to each other this way, neither may be destroyed.
Fix one side with weak_ptr:
struct B;
struct A {
std::shared_ptr<B> b;
};
struct B {
std::weak_ptr<A> a;
};
6. Using a smart pointer when no dynamic allocation is needed
Sometimes a plain stack variable is better:
int value = 42;
Do not use smart pointers unless dynamic lifetime or ownership is actually needed.
Comparisons
Smart pointer types compared
| Type | Ownership | Copyable | Automatically deletes? | Main use |
|---|---|---|---|---|
T* raw pointer | None by itself | Yes | No | Non-owning access or low-level code |
std::unique_ptr<T> | Exclusive | No | Yes | Single owner |
std::shared_ptr<T> | Shared | Yes | Yes | Multiple owners |
std::weak_ptr<T> | Non-owning observer | Yes | No | Observe shared_ptr object safely |
unique_ptr vs shared_ptr
| Feature | unique_ptr | shared_ptr |
|---|---|---|
| Ownership | One owner | Many owners |
| Performance | Usually lighter | More overhead due to reference counting |
| Copying | Not allowed | Allowed |
| Best default choice | Yes | Only when needed |
Smart pointers vs stack allocation
| Approach | When to use |
|---|---|
| Stack object | When lifetime is local and fixed |
unique_ptr | When object lifetime must outlive current scope or ownership must move |
shared_ptr | When several parts of code must jointly keep object alive |
Raw pointer vs smart pointer
A raw pointer can still be useful when it does not own the object:
void printUser(const User* user);
Here, user is just being accessed, not owned. Smart pointers are mainly for ownership management.
Cheat Sheet
Quick reference
Include header
#include <memory>
Create a unique_ptr
auto p = std::make_unique<MyClass>();
Create a shared_ptr
auto p = std::make_shared<MyClass>();
Create a weak_ptr
std::weak_ptr<MyClass> w = p;
Access the object
p->method();
(*p).method();
Move a unique_ptr
auto b = std::move(a);
Check for null
if (p) {
p->method();
}
Get raw pointer without transferring ownership
MyClass* raw = p.get();
Important rules
- Prefer stack allocation when possible
- Prefer
std::unique_ptrby default - Use
std::shared_ptronly for real shared ownership - Use
std::weak_ptrto break cycles or observe shared objects - Prefer
std::make_uniqueandstd::make_shared - Avoid manual
delete - Do not create multiple
shared_ptrs from the same raw pointer
Edge cases
unique_ptrcannot be copied- moved-from
unique_ptrbecomes empty weak_ptrmust be converted withlock()before use
Example:
if (auto s = w.lock()) {
s->method();
}
FAQ
What is the main purpose of a smart pointer in C++?
A smart pointer automatically manages the lifetime of dynamically allocated objects, reducing memory leaks and ownership bugs.
When should I use unique_ptr instead of shared_ptr?
Use unique_ptr when only one part of the program should own the object. This is the best default choice in modern C++.
Are raw pointers still used in modern C++?
Yes. Raw pointers are still useful for non-owning access, especially in function parameters and low-level code.
Why is make_unique preferred over new?
It is clearer, safer, and avoids manual memory management. It also makes ownership obvious.
What problem does weak_ptr solve?
It prevents circular ownership with shared_ptr and lets you observe an object without keeping it alive.
Do smart pointers only manage memory?
Mostly they are used for dynamic memory, but the underlying idea is automatic resource management through RAII.
Can I put smart pointers in containers like vector?
Yes. This is very common, especially std::vector<std::unique_ptr<Base>> for polymorphic objects.
Mini Project
Description
Build a small C++ program that manages a collection of shapes using smart pointers. This demonstrates a common real-world pattern: storing polymorphic objects safely without manual delete calls.
Goal
Create and use dynamically allocated objects with std::unique_ptr, store them in a container, and let C++ clean them up automatically.
Requirements
- Create a base class named
Shapewith a virtual methoddraw(). - Create at least two derived classes such as
CircleandSquare. - Store the objects in a
std::vector<std::unique_ptr<Shape>>. - Loop through the vector and call
draw()on each object. - Do not use manual
newordelete.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.