Skip to content

Smart_pointers

Smart pointers are wrapper classes that automatically manage memory. They are a key feature of modern C++ (C++11 and later) that help prevent memory leaks.

┌─────────────────────────────────────────────────────────────┐
│ Smart Pointers Overview │
├─────────────────────────────────────────────────────────────┤
│ │
│ Raw Pointer Problems: Smart Pointer Solutions: │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ • Memory leaks │ │ • Auto cleanup │ │
│ │ • Dangling ptrs │ │ • Exception safe │ │
│ │ • Double delete │ │ • Ownership │ │
│ │ • Exception unsafe│ │ semantics │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ Types: │
│ • unique_ptr - exclusive ownership │
│ • shared_ptr - shared ownership │
│ • weak_ptr - non-owning reference │
│ │
└─────────────────────────────────────────────────────────────┘

Exclusive ownership - only one pointer can own the resource:

#include <memory>
int main() {
// Create unique pointer
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
// Access value
std::cout << *ptr1 << "\n"; // 42
std::cout << ptr1.get() << "\n"; // Memory address
// Transfer ownership
std::unique_ptr<int> ptr2 = std::move(ptr1);
// ptr1 is now nullptr
// Release ownership
int* raw = ptr2.release();
delete raw;
}
// For arrays, specify type
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
arr[0] = 42;
std::cout << arr[0] << "\n";
// Automatically deletes[] when goes out of scope
#include <memory>
#include <fstream>
// Custom deleter for FILE
auto fileDeleter = [](FILE* f) {
if (f) fclose(f);
};
std::unique_ptr<FILE, decltype(fileDeleter)>
file(fopen("test.txt", "r"), fileDeleter);

Shared ownership - reference counted:

#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
// Copy creates another shared pointer
std::shared_ptr<int> ptr2 = ptr1;
std::cout << *ptr1 << "\n"; // 42
std::cout << *ptr2 << "\n"; // 42
std::cout << ptr1.use_count() << "\n"; // 2
// Reset releases ownership
ptr1.reset();
std::cout << ptr2.use_count() << "\n"; // 1
}
std::shared_ptr<int> ptr(
new int(42),
[](int* p) {
std::cout << "Custom deleter called\n";
delete p;
}
);
// Preferred way
auto ptr = std::make_shared<MyClass>(args);
// Why it's efficient: control block and object allocated together
// Control block contains: reference count, deleter, allocator

Non-owning reference to shared_ptr:

#include <memory>
int main() {
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;
// Check if expired
if (!weak.expired()) {
// Get shared_ptr from weak
std::shared_ptr<int> ptr = weak.lock();
std::cout << *ptr << "\n";
}
// Reset shared
shared.reset();
// Now expired
if (weak.expired()) {
std::cout << "weak_ptr expired\n";
}
}
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // Back-reference without ownership
~Node() { std::cout << "Node destroyed\n"; }
};
// unique_ptr to shared_ptr
std::unique_ptr<int> unique = std::make_unique<int>(42);
std::shared_ptr<int> shared = std::move(unique);
// shared_ptr to weak_ptr
std::weak_ptr<int> weak = shared;
// weak_ptr to shared_ptr (if still valid)
if (auto ptr = weak.lock()) {
// Use ptr
}
#include <vector>
#include <memory>
int main() {
// Vector of unique pointers
std::vector<std::unique_ptr<int>> vec;
vec.push_back(std::make_unique<int>(1));
vec.push_back(std::make_unique<int>(2));
// Can't copy unique_ptr, must move
// vec.push_back(unique); // Error!
vec.push_back(std::move(unique));
// Vector of shared pointers (can copy)
std::vector<std::shared_ptr<int>> svec;
svec.push_back(std::make_shared<int>(1));
svec.push_back(std::make_shared<int>(2));
}
#include <iostream>
#include <memory>
#include <vector>
class Resource {
private:
std::string name;
public:
Resource(const std::string& n) : name(n) {
std::cout << "Acquired: " << name << "\n";
}
~Resource() {
std::cout << "Released: " << name << "\n";
}
void use() {
std::cout << "Using: " << name << "\n";
}
};
int main() {
std::cout << "=== unique_ptr ===\n";
{
auto ptr = std::make_unique<Resource>("File");
ptr->use();
} // Automatically deleted
std::cout << "\n=== shared_ptr ===\n";
{
auto ptr1 = std::make_shared<Resource>("Memory");
auto ptr2 = ptr1;
std::cout << "use_count: " << ptr1.use_count() << "\n";
ptr1->use();
ptr2->use();
} // Deleted when last shared_ptr goes away
std::cout << "\n=== weak_ptr ===\n";
{
auto shared = std::make_shared<Resource>("Cache");
std::weak_ptr<Resource> weak = shared;
if (auto ptr = weak.lock()) {
ptr->use();
}
shared.reset();
if (weak.expired()) {
std::cout << "Resource expired\n";
}
}
return 0;
}
Use CaseSmart Pointer
Single ownerstd::unique_ptr
Multiple ownersstd::shared_ptr
Observer/reference without ownershipstd::weak_ptr
Old C APIsstd::unique_ptr with custom deleter
  1. Prefer std::make_unique and std::make_shared
  2. Use unique_ptr by default
  3. Only use shared_ptr when ownership is truly shared
  4. Use weak_ptr for caches and observers
  5. Don’t mix raw and smart pointers
  • Smart pointers automatically manage memory
  • unique_ptr for exclusive ownership
  • shared_ptr for shared ownership with reference counting
  • weak_ptr for non-owning references to shared resources
  • Use make_unique and make_shared for efficiency

Now let’s learn about modern C++ features that make memory management safer.

Next Chapter: 05_modern_cpp/24_auto_range_based.md - auto and Range-based for loops