Lesson 47
Smart Pointers
Covers unique_ptr, shared_ptr, and weak_ptr, their characteristics, creating and moving smart pointers, and managing dynamically allocated memory safely.
Lesson content
What are Smart Pointers in C++?
A smart pointer is an object that behaves like a regular pointer but automatically manages the lifetime of the memory it owns.
Unlike raw pointers, smart pointers automatically release memory when it is no longer needed. This follows the RAII (Resource Acquisition Is Initialization) principle, where resources are tied to object lifetimes.
Think of a smart pointer as a responsible caretaker. Instead of asking you to remember to clean up, it automatically does the cleanup when its job is finished.
Why do Smart Pointers exist?
Using raw pointers requires programmers to manually manage memory. Memory allocated by new remains occupied until explicitly freed, and forgetting to delete it causes a memory leak. Smart pointers eliminate this problem by automatically calling delete when appropriate.
Real-world use cases
- Modern C++ applications
- Game development
- GUI frameworks
- Database systems
- Web browsers
- Network servers
- Resource management
- Large enterprise software
Prerequisites
- Variables and data types
- Functions
- Classes and objects
- Pointers
- Dynamic memory (new and delete)
Including the header
#include <memory>Types of smart pointers
- unique_ptr – exclusive ownership, cannot share ownership, automatic memory release
- shared_ptr – shared ownership, can share ownership, automatic memory release
- weak_ptr – non-owning, doesn't own the resource, automatic memory release
unique_ptr
A unique_ptr owns a resource exclusively. Only one unique_ptr can own a particular object at a time. When the unique_ptr goes out of scope, the object is automatically destroyed.
unique_ptr<int> ptr = make_unique<int>(100);- Exclusive ownership
- Cannot be copied
- Can be moved
- Fastest smart pointer
- Minimal memory overhead
shared_ptr
A shared_ptr allows multiple pointers to own the same object. Internally, it maintains a reference count. When the last shared_ptr is destroyed, the object is automatically deleted.
shared_ptr<int> ptr = make_shared<int>(100);- Shared ownership
- Uses reference counting
- Slightly slower than unique_ptr
- Supports copying
weak_ptr
A weak_ptr observes an object managed by a shared_ptr without owning it. It does not increase the reference count. weak_ptr is mainly used to prevent cyclic references, where two or more shared_ptr objects keep each other alive indefinitely.
weak_ptr<int> observer = sharedPointer;- Does not own the object
- Does not increase the reference count
- Used with shared_ptr
- Prevents memory leaks caused by circular ownership
Example 1: Using unique_ptr
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> number = make_unique<int>(50);
cout << *number << endl;
return 0;
}
// Output: 50
// Memory is automatically releasedExample 2: Moving a unique_ptr
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> first = make_unique<int>(100);
unique_ptr<int> second = move(first);
if (first == nullptr)
{
cout << "first no longer owns the object." << endl;
}
cout << *second << endl;
return 0;
}
// Output:
// first no longer owns the object.
// 100Example 3: Using shared_ptr
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> first = make_shared<int>(200);
shared_ptr<int> second = first;
cout << "Reference Count: ";
cout << first.use_count();
return 0;
}
// Output: Reference Count: 2Example 4: Using weak_ptr
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> owner = make_shared<int>(500);
weak_ptr<int> observer = owner;
if (auto temp = observer.lock())
{
cout << *temp << endl;
}
return 0;
}
// Output: 500Example 5: Managing a student object
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class Student
{
public:
string name;
Student(string n) : name(n) {}
void display()
{
cout << "Student: " << name << endl;
}
};
int main()
{
unique_ptr<Student> student = make_unique<Student>("Alice");
student->display();
return 0;
}
// Output: Student: Alice
// Student object destroyed automaticallyCommon mistakes and pitfalls
- Copying a unique_ptr directly. Fix: use unique_ptr<int> p2 = move(p1) instead
- Using new directly with shared_ptr. Fix: prefer make_shared<T>()
- Using new directly with unique_ptr. Fix: prefer make_unique<T>()
- Creating cyclic shared_ptr references. Fix: use weak_ptr for one side of the relationship
- Calling delete on a smart pointer's managed object. Fix: let the smart pointer handle deletion automatically
Best practices
- Prefer std::unique_ptr whenever a single object owns a resource
- Use std::shared_ptr only when multiple objects truly need shared ownership
- Use std::weak_ptr to break circular ownership relationships
- Create smart pointers with make_unique() and make_shared() instead of calling new directly
- Avoid exposing raw pointers unless necessary, and never manually delete memory owned by a smart pointer
When not to use this
- The object has automatic (stack) storage duration
- Dynamic memory allocation is unnecessary
- You are working with small local variables that naturally go out of scope
- Performance-critical code requires custom memory management and you fully understand the trade-offs
Summary
- Smart Pointers in C++ automatically manage dynamically allocated memory
- They help prevent memory leaks and simplify resource management
- std::unique_ptr provides exclusive ownership and is the preferred choice in most cases
- std::shared_ptr enables shared ownership using reference counting
- std::weak_ptr observes objects managed by shared_ptr without affecting their lifetime and helps avoid cyclic references
- Use make_unique() and make_shared() to create smart pointers safely and efficiently
- Modern C++ code should favor smart pointers over raw pointers for ownership of dynamically allocated objects
Frequently asked questions
What is the difference between unique_ptr and shared_ptr?
A unique_ptr has exclusive ownership, meaning only one smart pointer owns the object. A shared_ptr allows multiple smart pointers to share ownership through reference counting.
Why should I use make_unique() instead of new?
make_unique() is safer, more concise, and avoids potential issues with exception handling and manual memory management.
What is the purpose of weak_ptr?
A weak_ptr provides non-owning access to an object managed by a shared_ptr. It is mainly used to prevent circular references that could otherwise cause memory leaks.
Can I copy a unique_ptr?
No. A unique_ptr cannot be copied because it enforces exclusive ownership. However, it can be moved using std::move().
Should I always use smart pointers instead of raw pointers?
For owning dynamically allocated resources, yes—smart pointers are generally the preferred choice in modern C++. Raw pointers are still useful for non-owning references, interoperability with existing APIs, or when no ownership semantics are involved.