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 .
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<>();
std::weak_ptr<> w = p;
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 .
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);
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 .
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 will later try to delete the same memory again.
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 |
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
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
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++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.
C++ Lambda Expressions Explained: What They Are and When to Use Them
Learn what C++ lambda expressions are, why they exist, when to use them, and how they simplify callbacks, algorithms, and local logic.