Introduction to Smart Pointers
Getting started with unique_ptr, shared_ptr, and weak_ptr for safer lifetimes.
In C++, dynamic memory management is traditionally done with new and delete.
However, manual memory management can lead to issues such as leaks, double deletion, or dangling pointer errors.
Modern C++ (C++11 and later) introduced the concept of smart pointers to solve these problems.
Smart pointers automate memory management and eliminate most of these errors.
1. What is a Smart Pointer?
A smart pointer is a special C++ class that manages the lifetime of an object. It behaves like a normal pointer but automatically controls the object’s lifecycle (creation and destruction). This helps prevent memory leaks.
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> ptr = make_unique<int>(42);
cout << *ptr << endl; // 42
}
There are three main smart pointer types: unique_ptr, shared_ptr, and weak_ptr.
All of them are defined in the <memory> header.
2. unique_ptr
std::unique_ptr is a pointer whose ownership is held by exactly one object.
Only one unique_ptr can own a given object at a time.
It cannot be copied, but it can be moved (using std::move()).
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p1 = make_unique<int>(10);
cout << *p1 << endl;
// unique_ptr cannot be copied:
// unique_ptr<int> p2 = p1; // ERROR!
// But it can be moved:
unique_ptr<int> p2 = move(p1);
cout << *p2 << endl;
}
Advantages:
- Automatic memory management
- Lightweight and efficient
- No need to call
deletemanually
unique_ptr cannot be copied, so it’s not suitable when the object must be shared.
3. shared_ptr
std::shared_ptr allows multiple pointers to share ownership of the same object.
The object’s lifetime depends on the number of owners (the reference count).
When the last shared_ptr is destroyed, the object is deleted automatically.
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> p1 = make_shared<int>(100);
shared_ptr<int> p2 = p1; // shared ownership
cout << "p1: " << *p1 << ", p2: " << *p2 << endl;
cout << "Use count: " << p1.use_count() << endl; // 2
}
Advantages:
- The same object can be used in multiple places
- No need for the user to call
delete
- Requires extra memory and work for reference counting
- Can cause memory leaks in circular reference situations
4. weak_ptr
std::weak_ptr is a “non-owning” reference to an object managed by a shared_ptr.
A weak_ptr does not extend the lifetime of the object; it only observes it.
This prevents circular reference problems.
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> sp = make_shared<int>(50);
weak_ptr<int> wp = sp;
cout << "Use count: " << sp.use_count() << endl;
if (auto ptr = wp.lock()) {
cout << "Value: " << *ptr << endl;
}
}
wp.lock() returns a valid shared_ptr if the object still exists;
if the object has been destroyed, it returns nullptr.
This provides safe access.
5. Circular Reference Problem
If two shared_ptr objects point to each other, the reference counts never reach zero
and the memory is never freed.
This typically occurs in linked or tree-like structures.
#include <memory>
using namespace std;
struct Node {
shared_ptr<Node> next;
// PROBLEM: circular reference!
};
int main() {
shared_ptr<Node> n1 = make_shared<Node>();
shared_ptr<Node> n2 = make_shared<Node>();
n1->next = n2;
n2->next = n1; // endless reference loop
}
The fix is to use a weak_ptr for one of the links:
struct Node {
weak_ptr<Node> next; // no more reference cycle
};
6. Using Smart Pointers with Functions
Smart pointers can be passed to functions just like regular pointers. However, you must pay attention to the ownership model.
void Print(shared_ptr<int> p) {
cout << *p << endl;
}
int main() {
auto ptr = make_shared<int>(77);
Print(ptr); // safe shared use
}
If ownership should be transferred, pass a unique_ptr and use std::move().
void Take(unique_ptr<int> p) {
cout << *p << endl;
}
int main() {
auto p = make_unique<int>(88);
Take(move(p)); // p is now nullptr
}
7. Advantages of Smart Pointer Memory Management
- No need to call
deletemanually. - Prevents memory leaks.
- Implements the RAII (Resource Acquisition Is Initialization) principle.
- Automatically cleans up memory in exception scenarios.
weak_ptrhelps avoid circular reference issues.
8. Comparison Table
| Type | Ownership | Copyable | Reference Counting | Usage |
|---|---|---|---|---|
unique_ptr |
Single | No | No | Objects with a single owner |
shared_ptr |
Shared | Yes | Yes | Objects shared across multiple places |
weak_ptr |
No ownership | Yes | Depends on a shared_ptr |
Breaking circular references |
9. TL;DR
unique_ptr→ single ownership, non-copyable, movable.shared_ptr→ shared ownership with reference counting.weak_ptr→ observes a shared object without owning it.- Smart pointers greatly reduce memory leaks.
- Use
weak_ptrto break circular references. - All examples can be run with Visual Studio 2022 or GCC 11+ compilers.