| name | cpp-smart-pointers |
| user-invocable | false |
| description | Use when managing memory safely in C++ with smart pointers including unique_ptr, shared_ptr, weak_ptr, and RAII patterns. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
C++ Smart Pointers and RAII
Master C++ smart pointers and Resource Acquisition Is Initialization (RAII)
patterns for automatic, exception-safe resource management. This skill covers
unique_ptr, shared_ptr, weak_ptr, custom deleters, and best practices for
modern C++ memory management.
RAII Principles
Resource Acquisition Is Initialization is a fundamental C++ idiom where
resource lifetime is tied to object lifetime.
Core Concept
void process_file_bad() {
FILE* file = fopen("data.txt", "r");
if (!file) return;
fclose(file);
}
void process_file_good() {
auto deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> file(fopen("data.txt", "r"), deleter);
if (!file) return;
}
class FileHandle {
FILE* file;
public:
explicit FileHandle(const char* filename, const char* mode)
: file(fopen(filename, mode)) {
if (!file) throw std::runtime_error("Failed to open file");
}
~FileHandle() {
if (file) fclose(file);
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&& other) noexcept : file(other.file) {
other.file = nullptr;
}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (file) fclose(file);
file = other.file;
other.file = nullptr;
}
return *this;
}
FILE* get() const { return file; }
};
RAII Benefits
void transaction() {
std::lock_guard<std::mutex> lock(mutex);
std::unique_ptr<Resource> resource = acquire_resource();
risky_operation();
}
std::unique_ptr<int[]> create_buffer(size_t size) {
auto buffer = std::make_unique<int[]>(size);
if (size > max_size) {
return nullptr;
}
initialize(buffer.get(), size);
return buffer;
}
Unique Ptr
std::unique_ptr provides exclusive ownership of dynamically allocated objects.
Unique Ptr Basic Usage
#include <memory>
std::unique_ptr<int> ptr1(new int(42));
auto ptr2 = std::make_unique<int>(100);
std::unique_ptr<int[]> arr(new int[10]);
auto arr2 = std::make_unique<int[]>(10);
class MyClass {
public:
MyClass(int x, std::string s) : value(x), name(s) {}
void print() const { std::cout << name << ": " << value << std::endl; }
private:
int value;
std::string name;
};
auto obj = std::make_unique<MyClass>(42, "Test");
obj->print();
Ownership Transfer
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int> ptr2 = std::move(ptr1);
void consume(std::unique_ptr<int> ptr) {
std::cout << *ptr << std::endl;
}
consume(std::move(ptr2));
std::unique_ptr<int> create() {
auto ptr = std::make_unique<int>(100);
return ptr;
}
auto result = create();
Custom Deleters
void custom_delete(int* ptr) {
std::cout << "Deleting: " << *ptr << std::endl;
delete ptr;
}
std::unique_ptr<int, decltype(&custom_delete)> ptr(new int(42), custom_delete);
auto deleter = [](int* ptr) {
std::cout << "Lambda delete: " << *ptr << std::endl;
delete ptr;
};
std::unique_ptr<int, decltype(deleter)> ptr2(new int(100), deleter);
auto file_deleter = [](FILE* f) {
if (f) {
std::cout << "Closing file" << std::endl;
fclose(f);
}
};
std::unique_ptr<FILE, decltype(file_deleter)> file(
fopen("data.txt", "r"),
file_deleter
);
struct SocketDeleter {
void operator()(int* socket) const {
(socket && *socket >= ) {
(*socket);
socket;
}
}
};
;
Unique Ptr Operations
std::unique_ptr<int> ptr = std::make_unique<int>(42);
int value = *ptr;
int* raw = ptr.get();
if (ptr) {
std::cout << "Owns resource" << std::endl;
}
int* released = ptr.release();
delete released;
ptr.reset();
ptr.reset(new int(100));
std::unique_ptr<int> ptr1 = std::make_unique<int>(1);
std::unique_ptr<int> ptr2 = std::make_unique<int>(2);
ptr1.swap(ptr2);
std::swap(ptr1, ptr2);
Shared Ptr
std::shared_ptr provides shared ownership with automatic reference counting.
Shared Ptr Basic Usage
#include <memory>
std::shared_ptr<int> ptr1(new int(42));
auto ptr2 = std::make_shared<int>(100);
auto ptr3 = ptr2;
auto ptr4 = ptr2;
std::cout << "Use count: " << ptr2.use_count() << std::endl;
{
auto ptr5 = ptr2;
}
Make Shared
auto ptr1 = std::make_shared<MyClass>(arg1, arg2);
func(std::shared_ptr<int>(new int(1)), std::shared_ptr<int>(new int(2)));
func(std::make_shared<int>(1), std::make_shared<int>(2));
std::shared_ptr<int[]> arr(new int[10]);
Shared Ptr Operations
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1;
int value = *ptr1;
int* raw = ptr1.get();
std::cout << "Count: " << ptr1.use_count() << std::endl;
std::cout << "Unique: " << ptr1.unique() << std::endl;
if (ptr1) {
std::cout << "Owns resource" << std::endl;
}
ptr1.reset();
ptr1.reset(new int(100));
ptr1 = nullptr;
ptr1.swap(ptr2);
std::swap(ptr1, ptr2);
Aliasing Constructor
struct Data {
int x;
int y;
};
auto data = std::make_shared<Data>();
data->x = 10;
data->y = 20;
std::shared_ptr<int> x_ptr(data, &data->x);
std::shared_ptr<int> y_ptr(data, &data->y);
Weak Ptr
std::weak_ptr provides non-owning references to shared_ptr-managed objects.
Weak Ptr Basic Usage
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;
std::cout << "Shared count: " << shared.use_count() << std::endl;
std::cout << "Weak count: " << weak.use_count() << std::endl;
if (!weak.expired()) {
if (auto locked = weak.lock()) {
std::cout << "Value: " << *locked << std::endl;
}
}
shared.reset();
if (weak.expired()) {
std::cout << "Object no longer exists" << std::endl;
}
Breaking Circular References
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "Node destroyed" << std::endl; }
};
void memory_leak() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1;
}
struct NodeFixed {
std::shared_ptr<NodeFixed> next;
std::weak_ptr<NodeFixed> prev;
~NodeFixed() { std::cout << "NodeFixed destroyed" << std::endl; }
};
void no_leak() {
auto node1 = std::make_shared<NodeFixed>();
auto node2 = std::make_shared<NodeFixed>();
node1->next = node2;
node2->prev = node1;
}
Observer Pattern
class Subject;
class Observer {
std::weak_ptr<Subject> subject;
public:
void observe(std::shared_ptr<Subject> s) {
subject = s;
}
void check() {
if (auto s = subject.lock()) {
std::cout << "Subject still exists" << std::endl;
} else {
std::cout << "Subject destroyed" << std::endl;
}
}
};
class Subject {
public:
void do_something() {
std::cout << "Subject doing something" << std::endl;
}
};
auto observer = std::make_shared<Observer>();
{
auto subject = std::make_shared<Subject>();
observer->observe(subject);
observer->check();
}
observer->check();
Cache Pattern
class ResourceCache {
std::unordered_map<std::string, std::weak_ptr<Resource>> cache;
public:
std::shared_ptr<Resource> get(const std::string& key) {
auto it = cache.find(key);
if (it != cache.end()) {
if (auto resource = it->second.lock()) {
return resource;
} else {
cache.erase(it);
}
}
auto resource = std::make_shared<Resource>(load_resource(key));
cache[key] = resource;
return resource;
}
void cleanup() {
for (auto it = cache.begin(); it != cache.end(); ) {
if (it->second.expired()) {
it = cache.erase(it);
} else {
++it;
}
}
}
};
Custom Deleters and Allocators
Advanced Deleter Patterns
template<typename T>
struct LoggingDeleter {
void operator()(T* ptr) const {
std::cout << "Deleting object at " << ptr << std::endl;
delete ptr;
}
};
std::unique_ptr<int, LoggingDeleter<int>> ptr(new int(42));
template<typename T>
struct ArrayDeleter {
void operator()(T* ptr) const {
delete[] ptr;
}
};
std::unique_ptr<int, ArrayDeleter<int>> arr(new int[10]);
template<typename T>
class ConditionalDeleter {
bool should_delete;
public:
explicit ConditionalDeleter(bool del = true) : should_delete(del) {}
void operator() {
(should_delete) {
ptr;
}
}
};
< T>
{
std::shared_ptr<ResourcePool<T>> pool;
:
}
{
pool->(ptr);
}
};
Custom Allocators
template<typename T>
class TrackingAllocator {
public:
using value_type = T;
TrackingAllocator() = default;
template<typename U>
TrackingAllocator(const TrackingAllocator<U>&) {}
T* allocate(std::size_t n) {
std::cout << "Allocating " << n << " objects" << std::endl;
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* ptr, std::size_t n) {
std::cout << "Deallocating " << n << " objects" << std::endl;
::operator delete(ptr);
}
};
auto ptr = std::allocate_shared<int>(TrackingAllocator<int>(), 42);
Smart Pointer Conversions
Safe Conversions
std::unique_ptr<int> unique = std::make_unique<int>(42);
std::shared_ptr<int> shared = std::move(unique);
std::weak_ptr<int> weak = shared;
if (auto locked = weak.lock()) {
}
int* raw = new int(42);
Downcasting with Smart Pointers
class Base {
public:
virtual ~Base() = default;
virtual void foo() = 0;
};
class Derived : public Base {
public:
void foo() override {}
void bar() {}
};
std::shared_ptr<Base> base = std::make_shared<Derived>();
std::shared_ptr<Derived> derived = std::static_pointer_cast<Derived>(base);
std::shared_ptr<Base> base2 = std::make_shared<Derived>();
if (auto derived2 = std::dynamic_pointer_cast<Derived>(base2)) {
derived2->bar();
}
std::shared_ptr<const int> const_ptr = std::make_shared<const int>(42);
std::shared_ptr<int> mutable_ptr = std::const_pointer_cast<int>(const_ptr);
Performance Considerations
Memory Overhead
sizeof(int*)
sizeof(std::unique_ptr<int>)
sizeof(std::shared_ptr<int>)
sizeof(std::weak_ptr<int>)
auto ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2(new int(42));
Performance Optimization
std::unique_ptr<Resource> create_resource() {
return std::make_unique<Resource>();
}
auto unique = create_resource();
std::shared_ptr<Resource> shared = std::move(unique);
void process(const std::shared_ptr<Resource>& res) {
}
std::shared_ptr<Resource> transfer(std::shared_ptr<Resource> res) {
return res;
}
class Observer {
std::weak_ptr<Subject> subject;
};
Exception Safety
Strong Exception Guarantee
class ExceptionSafe {
std::unique_ptr<Resource1> res1;
std::unique_ptr<Resource2> res2;
public:
void update(int value) {
auto new_res1 = std::make_unique<Resource1>(value);
auto new_res2 = std::make_unique<Resource2>(value);
res1 = std::move(new_res1);
res2 = std::move(new_res2);
}
};
RAII for Transactions
class Transaction {
std::unique_ptr<Connection> conn;
bool committed = false;
public:
explicit Transaction(std::unique_ptr<Connection> c)
: conn(std::move(c)) {
conn->begin_transaction();
}
~Transaction() {
if (!committed) {
try {
conn->rollback();
} catch (...) {
}
}
}
void commit() {
conn->commit();
committed = true;
}
};
void perform_transaction() {
auto conn = std::make_unique<Connection>();
Transaction txn(std::move(conn));
txn.commit();
}
Smart Pointers in Containers
Vectors of Smart Pointers
std::vector<std::unique_ptr<Widget>> widgets;
widgets.push_back(std::make_unique<Widget>(1));
widgets.push_back(std::make_unique<Widget>(2));
auto vec2 = std::move(widgets);
for (const auto& widget : vec2) {
widget->process();
}
vec2.erase(vec2.begin());
std::vector<std::shared_ptr<Widget>> shared_widgets;
shared_widgets.push_back(std::make_shared<Widget>(1));
auto shared_vec2 = shared_widgets;
Maps with Smart Pointers
std::map<std::string, std::unique_ptr<Resource>> resource_map;
resource_map["key1"] = std::make_unique<Resource>(1);
resource_map.emplace("key2", std::make_unique<Resource>(2));
auto it = resource_map.find("key1");
if (it != resource_map.end()) {
it->second->process();
}
auto extracted = std::move(resource_map["key1"]);
resource_map.erase("key1");
std::map<std::string, std::shared_ptr<Resource>> shared_map;
shared_map["key"] = std::make_shared<Resource>(1);
std::map<std::string, std::shared_ptr<Resource>> shared_map2;
shared_map2["key"] = shared_map["key"];
Common Patterns
Factory Pattern
class Product {
public:
virtual ~Product() = default;
virtual void use() = 0;
};
class ConcreteProductA : public Product {
public:
void use() override { std::cout << "Using A" << std::endl; }
};
class ConcreteProductB : public Product {
public:
void use() override { std::cout << "Using B" << std::endl; }
};
class Factory {
public:
static std::unique_ptr<Product> create(const std::string& type) {
if (type == "A") {
return std::make_unique<ConcreteProductA>();
} else if (type == "B") {
return std::make_unique<ConcreteProductB>();
}
return nullptr;
}
};
auto product = Factory::();
(product) {
product->();
}
Pimpl Idiom
class Widget {
public:
Widget();
~Widget();
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
void do_something();
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
class Widget::Impl {
public:
void do_something_impl() {
}
private:
std::vector<int> data;
std::string name;
};
Widget::Widget() : pimpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;
void Widget::do_something() {
pimpl->do_something_impl();
}
Singleton Pattern
class Singleton {
public:
static Singleton& instance() {
static Singleton instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
void do_something() {
std::cout << "Singleton method" << std::endl;
}
private:
Singleton() = default;
~Singleton() = default;
};
class ManagedSingleton {
public:
static std::shared_ptr<ManagedSingleton> instance() {
static auto inst = std::make_shared<ManagedSingleton>(PrivateTag{});
return inst;
}
private:
struct PrivateTag {};
public:
explicit ManagedSingleton {}
};
Best Practices
- Prefer make_unique and make_shared: More efficient and exception-safe
than using new directly
- Use unique_ptr by default: Only use shared_ptr when you actually need
shared ownership
- Pass smart pointers by const reference: Avoid unnecessary reference
count changes with shared_ptr
- Use weak_ptr to break cycles: Prevent memory leaks from circular
shared_ptr references
- Return by value for ownership transfer: Let move semantics handle
efficient transfer
- Never create multiple shared_ptrs from same raw pointer: Causes double
deletion
- Custom deleters for non-memory resources: Use for files, sockets,
mutexes, etc.
- Mark move operations noexcept: Enables optimizations in standard
containers
- Use smart pointers in containers: Allows containers of polymorphic
objects
- Don't mix smart pointers with raw pointer ownership: Choose one
ownership model
Common Pitfalls
- Creating shared_ptr from raw this pointer: Use enable_shared_from_this
instead
- Circular shared_ptr references: Use weak_ptr for back references or
parent pointers
- Creating multiple shared_ptrs from same raw pointer: Causes double
deletion
- Using get() to create new smart pointer: Breaks ownership model
- Forgetting to use move with unique_ptr: unique_ptr is not copyable
- Mixing smart pointers with manual delete: Use one ownership model
consistently
- Using shared_ptr when unique_ptr suffices: Unnecessary overhead
- Not checking weak_ptr.lock() return value: May return nullptr if object
deleted
- Custom deleter issues: Wrong deleter type or not handling nullptr
- Slicing with smart pointers: Store base class pointers to preserve
polymorphism
When to Use
Use this skill when:
- Managing dynamically allocated memory in C++
- Implementing RAII patterns for resource management
- Working with polymorphic objects in containers
- Preventing memory leaks and dangling pointers
- Implementing exception-safe code
- Creating factory patterns or object hierarchies
- Managing shared resources with reference counting
- Breaking circular dependencies with weak references
- Wrapping C APIs with automatic cleanup
- Teaching or learning modern C++ memory management
Resources