cpp

smart pointers

01_smart_pointers.cpp⚙️
/**
 * ============================================================
 * C++ SMART POINTERS
 * ============================================================
 * 
 * This file covers:
 * - unique_ptr
 * - shared_ptr
 * - weak_ptr
 * - make_unique and make_shared
 * - Custom deleters
 * 
 * Compile: g++ -std=c++17 -Wall 01_smart_pointers.cpp -o smart_pointers
 * Run: ./smart_pointers
 * 
 * ============================================================
 */

#include <iostream>
#include <memory>
#include <vector>
#include <string>

using namespace std;

// Demo class for demonstrations
class Resource {
private:
    string name;
    int id;
    static int nextId;
    
public:
    Resource(const string& n = "Unnamed") : name(n), id(nextId++) {
        cout << "  [+] Resource " << id << " (" << name << ") created" << endl;
    }
    
    ~Resource() {
        cout << "  [-] Resource " << id << " (" << name << ") destroyed" << endl;
    }
    
    void use() const {
        cout << "  [*] Using Resource " << id << " (" << name << ")" << endl;
    }
    
    string getName() const { return name; }
    int getId() const { return id; }
};

int Resource::nextId = 1;

// Forward declaration for circular reference demo
class Node;

int main() {
    cout << "============================================" << endl;
    cout << "     C++ SMART POINTERS" << endl;
    cout << "============================================" << endl << endl;

    // ========================================================
    // PART 1: WHY SMART POINTERS?
    // ========================================================
    
    cout << "--- PART 1: WHY SMART POINTERS? ---" << endl << endl;
    
    /*
     * Smart pointers automatically manage memory:
     * - No manual delete needed
     * - Exception safe
     * - Clear ownership semantics
     * - Prevent memory leaks and dangling pointers
     * 
     * Three types in C++11:
     * 1. unique_ptr - Exclusive ownership
     * 2. shared_ptr - Shared ownership
     * 3. weak_ptr   - Non-owning observer
     */
    
    cout << "Problems with raw pointers:" << endl;
    cout << "• Forget to delete → memory leak" << endl;
    cout << "• Delete twice → undefined behavior" << endl;
    cout << "• Exception before delete → leak" << endl;
    cout << "• Unclear ownership → confusion" << endl;
    cout << endl;
    cout << "Smart pointers solve these problems!" << endl;
    
    cout << endl;

    // ========================================================
    // PART 2: unique_ptr
    // ========================================================
    
    cout << "--- PART 2: unique_ptr (Exclusive Ownership) ---" << endl << endl;
    
    /*
     * unique_ptr:
     * - Only ONE pointer can own the resource
     * - Cannot be copied (only moved)
     * - Automatically deletes when it goes out of scope
     * - Zero overhead compared to raw pointer
     */
    
    cout << "Creating unique_ptr:" << endl;
    {
        // Method 1: Direct construction (older style)
        unique_ptr<Resource> ptr1(new Resource("First"));
        
        // Method 2: make_unique (preferred, C++14)
        auto ptr2 = make_unique<Resource>("Second");
        
        ptr1->use();
        ptr2->use();
        
        cout << "\nptr1 owns: " << (ptr1 ? ptr1->getName() : "nothing") << endl;
        cout << "ptr2 owns: " << (ptr2 ? ptr2->getName() : "nothing") << endl;
        
        // Cannot copy unique_ptr
        // unique_ptr<Resource> ptr3 = ptr1;  // ERROR!
        
        // Can move unique_ptr
        cout << "\nMoving ptr1 to ptr3:" << endl;
        unique_ptr<Resource> ptr3 = move(ptr1);  // Transfer ownership
        
        cout << "ptr1 owns: " << (ptr1 ? ptr1->getName() : "nothing") << endl;
        cout << "ptr3 owns: " << (ptr3 ? ptr3->getName() : "nothing") << endl;
        
        // Release ownership (returns raw pointer, you're responsible)
        cout << "\nReleasing ptr3:" << endl;
        Resource* raw = ptr3.release();
        cout << "ptr3 owns: " << (ptr3 ? ptr3->getName() : "nothing") << endl;
        delete raw;  // Manual delete needed after release!
        
        // Reset (delete current and optionally take new)
        cout << "\nResetting ptr2:" << endl;
        ptr2.reset();  // Deletes "Second"
        ptr2.reset(new Resource("New Second"));
        
        cout << "\nScope ending, automatic cleanup:" << endl;
    }  // ptr2 automatically deleted here
    
    cout << endl;

    // ========================================================
    // PART 3: unique_ptr WITH ARRAYS
    // ========================================================
    
    cout << "--- PART 3: unique_ptr WITH ARRAYS ---" << endl << endl;
    
    {
        // unique_ptr for arrays
        auto arr = make_unique<int[]>(5);
        
        for (int i = 0; i < 5; i++) {
            arr[i] = (i + 1) * 10;
        }
        
        cout << "Array via unique_ptr: ";
        for (int i = 0; i < 5; i++) {
            cout << arr[i] << " ";
        }
        cout << endl;
        
        // Automatically calls delete[] when scope ends
    }
    
    cout << "\n💡 For dynamic arrays, prefer std::vector!" << endl;
    
    cout << endl;

    // ========================================================
    // PART 4: shared_ptr
    // ========================================================
    
    cout << "--- PART 4: shared_ptr (Shared Ownership) ---" << endl << endl;
    
    /*
     * shared_ptr:
     * - Multiple pointers can share ownership
     * - Uses reference counting
     * - Deleted when last shared_ptr is destroyed
     * - Slight overhead (reference count)
     */
    
    cout << "Creating shared_ptr:" << endl;
    {
        // Method 1: Direct (not recommended)
        shared_ptr<Resource> sp1(new Resource("Shared1"));
        
        // Method 2: make_shared (preferred)
        auto sp2 = make_shared<Resource>("Shared2");
        
        cout << "sp1 use_count: " << sp1.use_count() << endl;
        cout << "sp2 use_count: " << sp2.use_count() << endl;
        
        // Copy shared_ptr (increases reference count)
        cout << "\nCopying sp2 to sp3:" << endl;
        shared_ptr<Resource> sp3 = sp2;
        
        cout << "sp2 use_count: " << sp2.use_count() << endl;
        cout << "sp3 use_count: " << sp3.use_count() << endl;
        
        {
            cout << "\nCreating sp4 (copy of sp2) in inner scope:" << endl;
            shared_ptr<Resource> sp4 = sp2;
            cout << "sp2 use_count: " << sp2.use_count() << endl;
        }
        cout << "After inner scope, sp2 use_count: " << sp2.use_count() << endl;
        
        // Reset one pointer
        cout << "\nResetting sp3:" << endl;
        sp3.reset();
        cout << "sp2 use_count: " << sp2.use_count() << endl;
        
        cout << "\nScope ending:" << endl;
    }
    
    cout << endl;

    // ========================================================
    // PART 5: weak_ptr
    // ========================================================
    
    cout << "--- PART 5: weak_ptr (Non-owning Observer) ---" << endl << endl;
    
    /*
     * weak_ptr:
     * - Does NOT own the resource
     * - Does NOT affect reference count
     * - Used to break circular references
     * - Must lock() to access (returns shared_ptr)
     */
    
    weak_ptr<Resource> weakPtr;
    
    {
        cout << "Creating shared_ptr:" << endl;
        auto sharedPtr = make_shared<Resource>("Observable");
        
        weakPtr = sharedPtr;  // weak_ptr observes
        
        cout << "shared_ptr use_count: " << sharedPtr.use_count() << endl;
        cout << "weak_ptr expired: " << boolalpha << weakPtr.expired() << endl;
        
        // Access through weak_ptr
        if (auto locked = weakPtr.lock()) {
            cout << "Locked weak_ptr, using: ";
            locked->use();
            cout << "use_count during lock: " << sharedPtr.use_count() << endl;
        }
        
        cout << "\nShared_ptr going out of scope:" << endl;
    }
    
    cout << "After shared_ptr destroyed:" << endl;
    cout << "weak_ptr expired: " << weakPtr.expired() << endl;
    
    if (auto locked = weakPtr.lock()) {
        locked->use();  // Won't execute
    } else {
        cout << "Cannot lock - resource is gone" << endl;
    }
    
    cout << noboolalpha << endl;

    // ========================================================
    // PART 6: CIRCULAR REFERENCE PROBLEM
    // ========================================================
    
    cout << "--- PART 6: CIRCULAR REFERENCES ---" << endl << endl;
    
    /*
     * Problem: Two shared_ptrs pointing to each other
     * Result: Memory leak (neither ever deleted)
     * Solution: Use weak_ptr for back-references
     */
    
    cout << "Circular reference causes memory leak:" << endl;
    cout << "  struct Node {" << endl;
    cout << "      shared_ptr<Node> next;" << endl;
    cout << "      shared_ptr<Node> prev;  // ← Problem!" << endl;
    cout << "  };" << endl;
    cout << endl;
    cout << "Solution - use weak_ptr:" << endl;
    cout << "  struct Node {" << endl;
    cout << "      shared_ptr<Node> next;" << endl;
    cout << "      weak_ptr<Node> prev;  // ← Fixed!" << endl;
    cout << "  };" << endl;
    
    cout << endl;

    // ========================================================
    // PART 7: make_unique AND make_shared
    // ========================================================
    
    cout << "--- PART 7: make_unique & make_shared ---" << endl << endl;
    
    cout << "Why use make_unique/make_shared?" << endl;
    cout << "─────────────────────────────────────────" << endl;
    cout << "1. Exception safety" << endl;
    cout << "   // Problem:" << endl;
    cout << "   func(shared_ptr<T>(new T), mayThrow());" << endl;
    cout << "   // If mayThrow() throws, new T might leak!" << endl;
    cout << endl;
    cout << "   // Solution:" << endl;
    cout << "   func(make_shared<T>(), mayThrow());" << endl;
    cout << "   // Safe: make_shared is one operation" << endl;
    cout << endl;
    cout << "2. Efficiency (make_shared)" << endl;
    cout << "   // One allocation for object + control block" << endl;
    cout << endl;
    cout << "3. Cleaner syntax" << endl;
    cout << "   auto p = make_unique<MyClass>(args...);" << endl;
    
    cout << endl;

    // ========================================================
    // PART 8: CUSTOM DELETERS
    // ========================================================
    
    cout << "--- PART 8: CUSTOM DELETERS ---" << endl << endl;
    
    cout << "Custom deleter for unique_ptr:" << endl;
    {
        auto deleter = [](Resource* ptr) {
            cout << "  Custom deleter called!" << endl;
            delete ptr;
        };
        
        unique_ptr<Resource, decltype(deleter)> ptr(new Resource("Custom"), deleter);
        ptr->use();
        cout << "Scope ending..." << endl;
    }
    
    cout << "\nCustom deleter for shared_ptr:" << endl;
    {
        auto ptr = shared_ptr<Resource>(
            new Resource("SharedCustom"),
            [](Resource* p) {
                cout << "  Custom shared_ptr deleter!" << endl;
                delete p;
            }
        );
        ptr->use();
        cout << "Scope ending..." << endl;
    }
    
    cout << "\n💡 Useful for: file handles, C APIs, etc." << endl;
    
    cout << endl;

    // ========================================================
    // PART 9: SMART POINTERS IN CONTAINERS
    // ========================================================
    
    cout << "--- PART 9: SMART POINTERS IN CONTAINERS ---" << endl << endl;
    
    cout << "Creating vector of shared_ptr:" << endl;
    {
        vector<shared_ptr<Resource>> resources;
        
        resources.push_back(make_shared<Resource>("Vec1"));
        resources.push_back(make_shared<Resource>("Vec2"));
        resources.push_back(make_shared<Resource>("Vec3"));
        
        cout << "\nUsing all resources:" << endl;
        for (const auto& r : resources) {
            r->use();
        }
        
        cout << "\nRemoving first element:" << endl;
        resources.erase(resources.begin());
        
        cout << "\nRemaining count: " << resources.size() << endl;
        cout << "\nClearing vector:" << endl;
        resources.clear();
    }
    
    cout << endl;

    // ========================================================
    // PART 10: SUMMARY AND GUIDELINES
    // ========================================================
    
    cout << "--- PART 10: SUMMARY ---" << endl << endl;
    
    cout << "When to use each smart pointer:" << endl;
    cout << "─────────────────────────────────────────" << endl;
    cout << "unique_ptr:" << endl;
    cout << "  • Exclusive ownership" << endl;
    cout << "  • Factory functions returning new objects" << endl;
    cout << "  • Members of classes (PIMPL idiom)" << endl;
    cout << endl;
    cout << "shared_ptr:" << endl;
    cout << "  • Shared ownership needed" << endl;
    cout << "  • Objects in multiple data structures" << endl;
    cout << "  • When ownership is truly shared" << endl;
    cout << endl;
    cout << "weak_ptr:" << endl;
    cout << "  • Breaking circular references" << endl;
    cout << "  • Caching (check if object still exists)" << endl;
    cout << "  • Observer pattern" << endl;
    
    cout << "\nGuidelines:" << endl;
    cout << "─────────────────────────────────────────" << endl;
    cout << "• Use unique_ptr by default" << endl;
    cout << "• Use make_unique/make_shared" << endl;
    cout << "• Pass by reference, not by smart pointer" << endl;
    cout << "• Pass unique_ptr to transfer ownership" << endl;
    cout << "• Return smart pointers from factories" << endl;
    
    cout << endl;

    cout << "============================================" << endl;
    cout << "SMART POINTERS SUMMARY:" << endl;
    cout << "============================================" << endl;
    cout << "• unique_ptr: single owner, zero overhead" << endl;
    cout << "• shared_ptr: multiple owners, ref counted" << endl;
    cout << "• weak_ptr: observer, breaks cycles" << endl;
    cout << "• Use make_unique/make_shared" << endl;
    cout << "• Prefer unique_ptr by default" << endl;
    cout << "============================================" << endl;

    return 0;
}

// ============================================================
// EXERCISES:
// ============================================================
/*
 * 1. Create a simple linked list using shared_ptr for next
 *    and weak_ptr for prev (doubly linked, no circular leak)
 * 
 * 2. Implement a simple cache class using weak_ptr:
 *    - Store weak_ptrs to objects
 *    - Return shared_ptr if object exists, null otherwise
 * 
 * 3. Create a factory function that returns unique_ptr to
 *    derived classes based on a parameter
 * 
 * 4. Implement the PIMPL idiom using unique_ptr
 * 
 * 5. Create a custom deleter for a file handle (FILE*)
 *    that calls fclose()
 */
Smart Pointers - C++ Tutorial | DeepML