| name | cpp-smart-pointers |
| user-invocable | false |
| description | Use when C++ smart pointers including unique_ptr, shared_ptr, and weak_ptr for automatic memory management following RAII principles. |
| allowed-tools | ["Read","Write","Edit","Grep","Glob","Bash"] |
C++ Smart Pointers
Smart pointers provide automatic memory management through RAII (Resource
Acquisition Is Initialization), eliminating manual new and delete calls.
They prevent memory leaks, dangling pointers, and double-free errors while
expressing ownership semantics clearly.
RAII Principles
RAII ties resource lifetime to object lifetime, ensuring automatic cleanup
when objects go out of scope.
#include <memory>
#include <iostream>
#include <fstream>
class FileHandle {
std::unique_ptr<std::FILE, decltype(&std::fclose)> file_;
public:
FileHandle(const char* filename, const char* mode)
: file_(std::fopen(filename, mode), &std::fclose) {
if (!file_) {
throw std::runtime_error("Failed to open file");
}
}
std::FILE* get() { return file_.get(); }
};
void manual_memory() {
int* ptr = new int(42);
delete ptr;
}
void raii_memory() {
auto ptr = std::make_unique<int>(42);
}
void multiple_resources() {
auto file1 = std::make_unique<FileHandle>("data.txt", "r");
auto file2 = std::make_unique<FileHandle>("output.txt", "w");
}
unique_ptr - Exclusive Ownership
unique_ptr represents exclusive ownership with zero runtime overhead and
move-only semantics.
#include <memory>
#include <vector>
#include <iostream>
class Widget {
int id_;
public:
Widget(int id) : id_(id) {
std::cout << "Widget " << id_ << " created\n";
}
~Widget() {
std::cout << "Widget " << id_ << " destroyed\n";
}
int id() const { return id_; }
};
void unique_ptr_basics() {
std::unique_ptr<Widget> w1(new Widget(1));
auto w2 = std::make_unique<Widget>(2);
std::cout << "Widget ID: " << w2->id() << "\n";
Widget* raw = w2.release();
delete raw;
w1.reset(new ());
Widget* ptr = w();
std::unique_ptr<Widget> w3 = std::(w1);
}
{
std::<Widget>(id);
}
{
std::vector<std::unique_ptr<Widget>> widgets;
widgets.(std::<Widget>());
widgets.(std::<Widget>());
w = std::(widgets[]);
}
{
{
(fp) {
std::cout << ;
std::(fp);
}
}
};
{
;
deleter = [](* p) {
std::cout << << *p << ;
p;
};
;
}
shared_ptr - Shared Ownership
shared_ptr enables shared ownership with reference counting, allowing
multiple pointers to the same object.
#include <memory>
#include <vector>
#include <iostream>
class Resource {
int id_;
public:
Resource(int id) : id_(id) {
std::cout << "Resource " << id_ << " created\n";
}
~Resource() {
std::cout << "Resource " << id_ << " destroyed\n";
}
int id() const { return id_; }
};
void shared_ptr_basics() {
std::shared_ptr<Resource> r1(new Resource(1));
auto r2 = std::make_shared<Resource>(2);
std::shared_ptr<Resource> r3 = r2;
std::cout << "Use count: " << r2.use_count() << "\n";
{
std::shared_ptr<Resource> r4 = r2;
std::cout << "Use count: " << r2.use_count() << "\n";
}
std::cout << << r() << ;
(r2) {
std::cout << ;
}
r();
std::cout << << r() << ;
}
{
:
value;
std::shared_ptr<Node> next;
( v) : (v), () {
std::cout << << value << ;
}
~() {
std::cout << << value << ;
}
};
{
head = std::<Node>();
head->next = std::<Node>();
head->next->next = std::<Node>();
}
{
u = std::<Resource>();
std::shared_ptr<Resource> s = std::(u);
}
{
x, y;
};
{
data = std::<Data>();
data->x = ;
data->y = ;
;
std::cout << << data.() << ;
}
weak_ptr - Breaking Cycles
weak_ptr provides non-owning references to shared_ptr objects, preventing
circular reference memory leaks.
#include <memory>
#include <iostream>
class BadParent;
class BadChild {
public:
std::shared_ptr<BadParent> parent;
~BadChild() { std::cout << "Child destroyed\n"; }
};
class BadParent {
public:
std::shared_ptr<BadChild> child;
~BadParent() { std::cout << "Parent destroyed\n"; }
};
void circular_reference_leak() {
auto parent = std::make_shared<BadParent>();
auto child = std::make_shared<BadChild>();
parent->child = child;
child->parent = parent;
}
class Parent;
class Child {
public:
std::weak_ptr<Parent> parent;
~Child() { std::cout << "Child destroyed\n"; }
};
class Parent {
public:
std::shared_ptr<Child> child;
~Parent() { std::cout << "Parent destroyed\n"; }
};
void weak_ptr_example() {
parent = std::<Parent>();
child = std::<Child>();
parent->child = child;
child->parent = parent;
}
{
std::weak_ptr<Resource> weak;
{
shared = std::<Resource>();
weak = shared;
std::cout << << shared.() << ;
std::cout << << weak.() << ;
( locked = weak.()) {
std::cout << << locked->()
<< ;
std::cout << << locked.() << ;
}
}
( locked = weak.()) {
std::cout << ;
} {
std::cout << ;
}
std::cout << << weak.() << ;
}
{
std::vector<std::weak_ptr< Observer>> observers_;
:
{
observers_.(observer);
}
{
observers_.(
std::(observers_.(), observers_.(),
[]( & weak) { weak.(); }),
observers_.()
);
(& weak : observers_) {
( observer = weak.()) {
}
}
}
};
enable_shared_from_this
enable_shared_from_this allows objects to create shared_ptr instances
pointing to themselves safely.
#include <memory>
#include <iostream>
#include <vector>
class Task : public std::enable_shared_from_this<Task> {
int id_;
std::vector<std::shared_ptr<Task>> dependencies_;
public:
Task(int id) : id_(id) {}
void add_dependency(std::shared_ptr<Task> dep) {
dependencies_.push_back(dep);
}
void register_with(std::shared_ptr<Task> other) {
other->add_dependency(shared_from_this());
}
int id() const { return id_; }
};
void shared_from_this_example() {
auto task1 = std::make_shared<Task>(1);
auto task2 = std::make_shared<Task>(2);
task1->register_with(task2);
}
class Button : std::enable_shared_from_this<Button> {
Callback = std::function<(std::shared_ptr<Button>)>;
Callback on_click_;
:
{
on_click_ = callback;
}
{
(on_click_) {
(());
}
}
};
{
button = std::<Button>();
button->([](std::shared_ptr<Button> btn) {
std::cout << ;
});
button->();
}
Custom Deleters
Custom deleters enable smart pointers to manage non-memory resources like
file handles, sockets, and database connections.
#include <memory>
#include <iostream>
#include <cstdio>
void close_file(std::FILE* fp) {
if (fp) {
std::cout << "Closing file\n";
std::fclose(fp);
}
}
void shared_ptr_deleter() {
std::shared_ptr<std::FILE> file(
std::fopen("data.txt", "r"),
[](std::FILE* fp) {
if (fp) {
std::cout << "Lambda closing file\n";
std::fclose(fp);
}
}
);
}
void array_deleter() {
std::unique_ptr<int[]> arr(new int[10]);
arr[0] = 42;
std::unique_ptr<int[], void(*)(int*)> custom_arr(
[],
[](* p) {
std::cout << ;
[] p;
}
);
}
< T>
{
std::vector<std::unique_ptr<T>> pool_;
:
{
(pool_.()) {
std::<T>(
(),
[](T* ptr) { ->(ptr); }
);
}
ptr = pool_.().();
pool_.();
std::<T>(
ptr,
[](T* p) { ->(p); }
);
}
:
{
pool_.(std::<T>(ptr));
}
};
Performance Considerations
Smart pointers have different performance characteristics that influence
design decisions.
#include <memory>
#include <chrono>
#include <iostream>
struct Data {
int values[100];
};
void performance_comparison() {
using namespace std::chrono;
{
auto start = high_resolution_clock::now();
for (int i = 0; i < 1000000; ++i) {
auto ptr = std::make_unique<Data>();
}
auto end = high_resolution_clock::now();
std::cout << "unique_ptr: "
<< duration_cast<milliseconds>(end - start).count()
<< "ms\n";
}
{
auto start = high_resolution_clock::now();
for (int i = 0; i < 1000000; ++i) {
auto ptr = std::make_shared<Data>();
}
auto end = high_resolution_clock::now();
std::cout << "shared_ptr: "
<< duration_cast<milliseconds>(end - start).count()
<< ;
}
{
ptr = std::<Data>();
start = high_resolution_clock::();
( i = ; i < ; ++i) {
copy = ptr;
}
end = high_resolution_clock::();
std::cout <<
<< <milliseconds>(end - start).()
<< ;
}
}
{
s1 = std::<Data>();
;
}
Thread Safety
Smart pointers provide specific thread-safety guarantees that must be
understood for concurrent programming.
#include <memory>
#include <thread>
#include <vector>
#include <iostream>
void thread_safety() {
auto shared = std::make_shared<int>(42);
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([shared]() {
auto copy = shared;
std::cout << *copy << "\n";
});
}
for (auto& t : threads) {
t.join();
}
auto data = std::make_shared<int>(0);
std::vector<std::thread> unsafe_threads;
for (int i = 0; i < 10; ++i) {
unsafe_threads.emplace_back([data]() {
++(*data);
});
}
for (auto& t : unsafe_threads) {
t.join();
}
}
void atomic_shared_ptr {
std::shared_ptr<> shared = std::<>();
;
;
t();
t();
}
Best Practices
- Prefer
make_unique and make_shared over explicit new for exception
safety and efficiency
- Use
unique_ptr by default; only use shared_ptr when shared ownership is
truly needed
- Use
weak_ptr to break circular references in parent-child relationships
- Never call
delete on raw pointers obtained from get()
- Pass
unique_ptr by value to transfer ownership, by reference to use
without transferring
- Pass
shared_ptr by const reference to observe, by value to share
ownership
- Use custom deleters for managing non-memory resources
- Avoid creating
shared_ptr from raw pointers multiple times (creates
multiple control blocks)
- Mark move operations as
noexcept when implementing custom smart
pointer-like types
- Use
enable_shared_from_this when objects need to create shared_ptr to
themselves
Common Pitfalls
- Creating multiple
shared_ptr instances from same raw pointer, causing
double-free
- Storing
shared_ptr in containers when unique_ptr would suffice, wasting
memory
- Forgetting to break circular references with
weak_ptr, causing memory
leaks
- Calling
shared_from_this() before object is managed by shared_ptr
- Passing smart pointers by value unnecessarily, copying reference count
- Using
reset() instead of assignment, potentially destroying objects
prematurely
- Assuming thread-safety of pointed-to object (only control block is
thread-safe)
- Not checking if
weak_ptr::lock() succeeds before using returned
shared_ptr
- Using
unique_ptr<T> for arrays without unique_ptr<T[]> syntax
- Mixing manual memory management with smart pointers in same codebase
When to Use Smart Pointers
Use smart pointers when you need:
- Automatic memory management without garbage collection
- Clear expression of ownership semantics in your API
- Exception-safe resource management following RAII
- Prevention of memory leaks in complex control flow
- Shared ownership of objects across multiple components
- Breaking circular references with weak pointers
- Management of non-memory resources with custom deleters
- Modern C++ code that avoids manual
new and delete
- Thread-safe reference counting for concurrent access
- Interoperability with standard library containers and algorithms
Resources