| 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(3));
Widget* ptr = w1.get();
std::unique_ptr<Widget> w3 = std::move(w1);
}
std::unique_ptr<Widget> create_widget(int id) {
return std::make_unique<Widget>(id);
}
void container_example() {
std::vector<std::unique_ptr<Widget>> widgets;
widgets.push_back(std::make_unique<Widget>(1));
widgets.push_back(std::make_unique<Widget>(2));
auto w = std::move(widgets[0]);
}
struct FileCloser {
void operator()(std::FILE* fp) const {
if (fp) {
std::cout << "Closing file\n";
std::fclose(fp);
}
}
};
void custom_deleter_example() {
std::unique_ptr<std::FILE, FileCloser> file(
std::fopen("data.txt", "r")
);
auto deleter = [](int* p) {
std::cout << "Deleting: " << *p << "\n";
delete p;
};
std::unique_ptr<int, decltype(deleter)> ptr(new int(42), deleter);
}
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 << "Use count: " << r2.use_count() << "\n";
if (r2) {
std::cout << "r2 is valid\n";
}
r2.reset();
std::cout << "Use count: " << r3.use_count() << "\n";
}
class Node {
public:
int value;
std::shared_ptr<Node> next;
Node(int v) : value(v), next(nullptr) {
std::cout << "Node " << value << " created\n";
}
~Node() {
std::cout << "Node " << value << " destroyed\n";
}
};
void linked_list_example() {
auto head = std::make_shared<Node>(1);
head->next = std::make_shared<Node>(2);
head->next->next = std::make_shared<Node>(3);
}
void pointer_conversion() {
auto u = std::make_unique<Resource>(1);
std::shared_ptr<Resource> s = std::move(u);
}
struct Data {
int x, y;
};
void aliasing_example() {
auto data = std::make_shared<Data>();
data->x = 10;
data->y = 20;
std::shared_ptr<int> px(data, &data->x);
std::cout << "Use count: " << data.use_count() << "\n";
}
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() {
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
parent->child = child;
child->parent = parent;
}
void weak_ptr_usage() {
std::weak_ptr<Resource> weak;
{
auto shared = std::make_shared<Resource>(1);
weak = shared;
std::cout << "Use count: " << shared.use_count() << "\n";
std::cout << "Weak count: " << weak.use_count() << "\n";
if (auto locked = weak.lock()) {
std::cout << "Resource still alive: " << locked->id()
<< "\n";
std::cout << "Use count: " << locked.use_count() << "\n";
}
}
if (auto locked = weak.lock()) {
std::cout << "Resource still alive\n";
} else {
std::cout << "Resource destroyed\n";
}
std::cout << "Expired: " << weak.expired() << "\n";
}
class Observable {
std::vector<std::weak_ptr<class Observer>> observers_;
public:
void attach(std::shared_ptr<Observer> observer) {
observers_.push_back(observer);
}
void notify() {
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[](const auto& weak) { return weak.expired(); }),
observers_.end()
);
for (auto& weak : observers_) {
if (auto observer = weak.lock()) {
}
}
}
};
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 : public std::enable_shared_from_this<Button> {
using Callback = std::function<void(std::shared_ptr<Button>)>;
Callback on_click_;
public:
void set_on_click(Callback callback) {
on_click_ = callback;
}
void click() {
if (on_click_) {
on_click_(shared_from_this());
}
}
};
void callback_example() {
auto button = std::make_shared<Button>();
button->set_on_click([](std::shared_ptr<Button> btn) {
std::cout << "Button clicked!\n";
});
button->click();
}
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(
new int[10],
[](int* p) {
std::cout << "Custom array delete\n";
delete[] p;
}
);
}
template<typename T>
class ResourcePool {
std::vector<std::unique_ptr<T>> pool_;
public:
std::shared_ptr<T> acquire() {
if (pool_.empty()) {
return std::shared_ptr<T>(
new T(),
[this](T* ptr) { this->release(ptr); }
);
}
auto ptr = pool_.back().release();
pool_.pop_back();
return std::shared_ptr<T>(
ptr,
[this](T* p) { this->release(p); }
);
}
private:
void release(T* ptr) {
pool_.push_back(std::unique_ptr<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()
<< "ms\n";
}
{
auto ptr = std::make_shared<Data>();
auto start = high_resolution_clock::now();
for (int i = 0; i < 1000000; ++i) {
auto copy = ptr;
}
auto end = high_resolution_clock::now();
std::cout << "shared_ptr copy: "
<< duration_cast<milliseconds>(end - start).count()
<< "ms\n";
}
}
void allocation_efficiency() {
auto s1 = std::make_shared<Data>();
std::shared_ptr<Data> s2(new 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<int> shared = std::make_shared<int>(42);
std::thread t1([&shared]() {
auto local = std::atomic_load(&shared);
});
std::thread t2([&shared]() {
auto new_value = std::make_shared<int>(100);
std::atomic_store(&shared, new_value);
});
t1.join();
t2.join();
}
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