Smart Pointers

Smart pointers automate memory management, preventing leaks and simplifying ownership. std::unique_ptr enforces exclusive ownership and is lightweight; std::shared_ptr allows shared ownership via reference counting; std::weak_ptr breaks reference cycles. They call the appropriate destructor automatically when the pointer goes out of scope. Using smart pointers eliminates most manual delete calls, making code exception-safe and easier to reason about, which is foundational for robust modern C++ applications.

🧠 What are Smart Pointers?

Smart pointers are C++ objects that automatically manage memory allocation and deallocation, preventing memory leaks and dangling pointers while providing safer alternatives to raw pointers.


#include <memory>

// Smart pointer automatically cleans up memory
std::unique_ptr<int> ptr = std::make_unique<int>(42);
std::cout << *ptr << std::endl; // Output: 42
                                    

Output:

42

Types of Smart Pointers

🔒

unique_ptr

Exclusive ownership of a resource

std::unique_ptr<int> ptr = std::make_unique<int>(10);
🤝

shared_ptr

Shared ownership with reference counting

std::shared_ptr<int> ptr = std::make_shared<int>(20);
👻

weak_ptr

Non-owning observer to break cycles

std::weak_ptr<int> weak = shared_ptr;

Benefits

Automatic cleanup and safety

// No delete needed!

🔹 unique_ptr Example

std::unique_ptr provides exclusive ownership of a dynamically allocated object, automatically deleting it when the pointer goes out of scope. It is non-copyable but movable, making it ideal for resource management in a single-owner context. For example, auto ptr = std::make_unique<Player>("Alice"); ensures the Player is destroyed automatically. This eliminates memory leaks, enforces clear ownership semantics, and is essential for implementing RAII in modern C++ applications.

#include <iostream>
#include <memory>

class Player {
public:
    Player(std::string name) : name_(name) {
        std::cout << "Player " << name_ << " created\n";
    }
    ~Player() {
        std::cout << "Player " << name_ << " destroyed\n";
    }
    void play() { std::cout << name_ << " is playing\n"; }
private:
    std::string name_;
};

int main() {
    std::unique_ptr<Player> player = std::make_unique<Player>("Alice");
    player->play();
    // Automatic cleanup when player goes out of scope
    return 0;
}

Output:

Player Alice created
Alice is playing
Player Alice destroyed

🔹 shared_ptr Example

std::shared_ptr enables shared ownership of an object using reference counting, deleting it when the last shared_ptr is destroyed. Multiple pointers can manage the same resource, making it suitable for shared data structures. For example, auto sp1 = std::make_shared<int>(100); auto sp2 = sp1; shares ownership. This simplifies lifetime management in complex scenarios but requires caution to avoid circular references, which can be resolved with std::weak_ptr.

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(100);
    std::cout << "Reference count: " << ptr1.use_count() << std::endl;
    
    {
        std::shared_ptr<int> ptr2 = ptr1; // Share ownership
        std::cout << "Reference count: " << ptr1.use_count() << std::endl;
        std::cout << "Value: " << *ptr2 << std::endl;
    } // ptr2 goes out of scope
    
    std::cout << "Reference count: " << ptr1.use_count() << std::endl;
    return 0;
}

Output:

Reference count: 1
Reference count: 2
Value: 100
Reference count: 1

🔹 weak_ptr Example

std::weak_ptr holds a non-owning reference to an object managed by std::shared_ptr, preventing circular references. It does not increase the reference count and must be converted to a shared_ptr to access the object. For example, weak_ptr can break cycles in graph structures. This ensures objects are properly deallocated when no strong references remain, enhancing memory safety in interconnected data models like caches and observer patterns.

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> shared = std::make_shared<int>(42);
    std::weak_ptr<int> weak = shared;
    
    std::cout << "shared use_count: " << shared.use_count() << std::endl;
    
    if (auto locked = weak.lock()) { // Convert to shared_ptr safely
        std::cout << "Value: " << *locked << std::endl;
    }
    
    shared.reset(); // Release shared ownership
    
    if (weak.expired()) {
        std::cout << "Object has been destroyed" << std::endl;
    }
    
    return 0;
}

Output:

shared use_count: 1
Value: 42
Object has been destroyed

🧠 Test Your Knowledge

Which smart pointer provides exclusive ownership?