C++ Smart Pointers: unique_ptr, shared_ptr, weak_ptr & RAII Memory Complete Masterclass

โšก Modern C++ (C++17 / C++20 / C++23) ๐ŸŸข Lesson 17 ๐Ÿ“‚ Phase 17: Smart Pointers & Memory ๐Ÿ“… 2026 Master Edition
๐Ÿ“Œ Covered in this in-depth guide: Stack vs Heap Layout ยท Raw Pointer Problems ยท unique_ptr & make_unique ยท Ownership Transfer & Move ยท Sink/Borrow Patterns ยท shared_ptr & use_count ยท Control Block ยท Aliasing Constructor ยท weak_ptr & Cycles ยท Observer Pattern ยท Custom Deleters ยท Rule of Zero ยท enable_shared_from_this

Welcome to Phase 17 (Chapter 17): C++ Smart Pointers & Memory Management Masterclass! Modern C++ eliminates manual new/delete through RAII-based smart pointers. std::unique_ptr for exclusive ownership, std::shared_ptr for shared reference-counted ownership, and std::weak_ptr for non-owning observation. Together they make memory-safe, leak-free C++ achievable without a garbage collector.

1Stack vs Heap โ€” Memory Model
C++ Memory Layout: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ TEXT (code / executable instructions) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ DATA (global/static initialized variables) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ BSS (global/static uninitialized variables) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ HEAP (dynamic allocation via new/malloc) โ”‚ โ”‚ grows upward โ†‘ โ”‚ โ”‚ ... โ”‚ โ”‚ grows downward โ†“ โ”‚ โ”‚ STACK (local variables, function frames) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ STACK: HEAP: โœ… Automatic lifetime โŒ Manual lifetime (new/delete) โœ… Very fast allocation โœ… Large, flexible allocations โœ… No fragmentation โš ๏ธ Fragmentation possible โš ๏ธ Limited size (~8MB) โŒ Memory leaks if forgot delete
C++ โ€” Raw pointer problemsโ–ถ Run in Compiler
#include <iostream>
#include <stdexcept>

// โ”€โ”€โ”€ Memory leak example โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
void memoryLeakExample() {
    int* p = new int(42);
    // If we throw or return early โ€” leak! delete never called
    if (true) return;   // <-- LEAK: p is lost!
    delete p;
}

// โ”€โ”€โ”€ Dangling pointer example โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
int* danglingPointer() {
    int local = 42;
    return &local;   // DANGER: local is destroyed when function returns!
}

// โ”€โ”€โ”€ Double deletion example โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
void doubleDeletion() {
    int* p = new int(99);
    delete p;
    // delete p;    // CRASH: undefined behaviour!
    p = nullptr;    // Good practice: null after delete
}

// โ”€โ”€โ”€ Exception-unsafe raw new โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
void processData() {
    int* data = new int[1000];
    // ... some code that might throw ...
    // throw std::runtime_error("error!"); // <-- LEAK: data never deleted!
    delete[] data;   // not reached if exception thrown
}

// โ”€โ”€โ”€ The RAII solution: smart pointers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// (covered in sections below)
2std::unique_ptr โ€” Exclusive Single Ownership

unique_ptr Contract:

โ€ข Exactly ONE unique_ptr owns the resource at any time. Cannot be copied โ€” only moved.

โ€ข Resource is automatically destroyed when the unique_ptr goes out of scope (RAII).

โ€ข Always create with std::make_unique<T>(args) โ€” exception-safe and avoids raw new.

โ€ข Zero overhead โ€” same size and cost as a raw pointer at runtime.

C++ โ€” unique_ptr: creation, ownership, arraysโ–ถ Run in Compiler
#include <iostream>
#include <memory>
#include <string>
#include <vector>

class File {
    std::string path_;
    bool open_;
public:
    explicit File(std::string path) : path_{std::move(path)}, open_{true} {
        std::cout << "Opened: " << path_ << "
";
    }
    ~File() {
        if (open_) std::cout << "Closed: " << path_ << "
";
    }
    void write(const std::string& data) {
        if (!open_) throw std::runtime_error("File not open!");
        std::cout << path_ << " << " << data << "
";
    }
    void close() { open_ = false; std::cout << "Manually closed: " << path_ << "
"; }
};

// Factory function returning unique_ptr
std::unique_ptr<File> openFile(const std::string& path) {
    return std::make_unique<File>(path);  // RAII from the start
}

// Function that takes ownership (sink)
void processFile(std::unique_ptr<File> file) {
    file->write("Processing data...");
}  // file destroyed here automatically

// Function that borrows (non-owning reference)
void readFromFile(const File& file) {
    std::cout << "Reading from file
";
}

// Function that uses (non-owning raw pointer)
void updateFile(File* file) {
    if (file) file->write("Updated!");
}

int main() {
    // Create with make_unique (ALWAYS prefer this!)
    auto f1 = std::make_unique<File>("data.txt");
    f1->write("Hello World");

    // Borrow without transferring ownership
    readFromFile(*f1);        // pass by reference
    updateFile(f1.get());     // get() returns raw pointer โ€” non-owning!

    // Transfer ownership (move semantics)
    auto f2 = std::move(f1);  // f2 now owns the File
    if (!f1) std::cout << "f1 is now null after move
";
    f2->write("Written via f2");

    // Sink function โ€” takes ownership, destroys at end of function
    processFile(std::move(f2));
    if (!f2) std::cout << "f2 is null after move-to-sink
";

    // Factory function
    auto logFile = openFile("server.log");
    logFile->write("Server started");

    // unique_ptr to array
    auto buffer = std::make_unique<char[]>(1024);
    buffer[0] = 'H'; buffer[1] = 'i'; buffer[2] = '';
    std::cout << "buffer: " << buffer.get() << "
";

    // Vector of unique_ptrs (polymorphic collection)
    std::vector<std::unique_ptr<File>> filePool;
    for (const std::string& name : {"a.txt", "b.txt", "c.txt"}) {
        filePool.push_back(std::make_unique<File>(name));
    }
    for (auto& f : filePool) f->write("batch write");

    return 0;
}  // All files automatically closed! โœ…
3std::shared_ptr โ€” Shared Reference-Counted Ownership
shared_ptr Internal Layout: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ shared_ptr<T> p1 โ”‚โ”€โ”€โ”ฌโ”€โ”€โ–บโ”‚ Control Block (heap) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ use_count = 2 โ”‚ โ”‚ โ”‚ weak_count = 1 โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ deleter โ”‚ โ”‚ shared_ptr<T> p2 โ”‚โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ T object (heap)โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ When use_count reaches 0 โ†’ object destroyed When weak_count also 0 โ†’ control block destroyed
C++ โ€” shared_ptr: reference counting, use_countโ–ถ Run in Compiler
#include <iostream>
#include <memory>
#include <vector>
#include <string>

class Database {
    std::string name_;
    int queryCount_{0};
public:
    explicit Database(std::string name) : name_{std::move(name)} {
        std::cout << "DB '" << name_ << "' connected
";
    }
    ~Database() { std::cout << "DB '" << name_ << "' disconnected
"; }

    void query(const std::string& sql) {
        ++queryCount_;
        std::cout << name_ << " query #" << queryCount_ << ": " << sql << "
";
    }
    int queryCount() const { return queryCount_; }
};

class UserService {
    std::shared_ptr<Database> db_;  // shared ownership
public:
    explicit UserService(std::shared_ptr<Database> db) : db_{std::move(db)} {}
    void getUser(int id) { db_->query("SELECT * FROM users WHERE id=" + std::to_string(id)); }
};

class OrderService {
    std::shared_ptr<Database> db_;
public:
    explicit OrderService(std::shared_ptr<Database> db) : db_{std::move(db)} {}
    void getOrders(int userId) { db_->query("SELECT * FROM orders WHERE user_id=" + std::to_string(userId)); }
};

int main() {
    // Shared database connection
    auto db = std::make_shared<Database>("PostgreSQL");
    std::cout << "use_count after creation: " << db.use_count() << "
";  // 1

    {
        UserService  userSvc{db};   // db shared with UserService
        OrderService orderSvc{db};  // db shared with OrderService
        std::cout << "use_count with 2 services: " << db.use_count() << "
";  // 3

        userSvc.getUser(42);
        orderSvc.getOrders(42);

        // Copy shared_ptr โ€” increases ref count
        auto db2 = db;
        auto db3 = db;
        std::cout << "use_count with copies: " << db.use_count() << "
";  // 5

        db2.reset();  // release one owner
        std::cout << "after db2.reset: " << db.use_count() << "
";  // 4
    }  // userSvc, orderSvc, db3 destroyed โ€” ref count decreases
    std::cout << "use_count after scope: " << db.use_count() << "
";   // 1

    // Aliasing constructor โ€” shared_ptr to a member of an object
    struct Config { int timeout = 30; std::string host = "localhost"; };
    auto config = std::make_shared<Config>();
    // shared_ptr to the host member โ€” shares ownership of the whole Config!
    std::shared_ptr<std::string> hostPtr(config, &config->host);
    std::cout << "host: " << *hostPtr << " config use_count: " << config.use_count() << "
";
    config.reset();  // Config survives because hostPtr still holds it!
    std::cout << "host after config.reset: " << *hostPtr << "
";

    return 0;
}  // DB disconnected here (use_count hits 0) โœ…
4std::weak_ptr โ€” Breaking Cyclic References
C++ โ€” weak_ptr: observe without owning, cycle fixโ–ถ Run in Compiler
#include <iostream>
#include <memory>
#include <string>
#include <vector>

// โ”€โ”€โ”€ Cycle with shared_ptr (MEMORY LEAK!) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
struct BadNode {
    int val;
    std::shared_ptr<BadNode> next;  // strong reference
    std::shared_ptr<BadNode> prev;  // strong reference โ† CYCLE!
    explicit BadNode(int v) : val{v} { std::cout << "BadNode " << v << " created
"; }
    ~BadNode() { std::cout << "BadNode " << val << " destroyed
"; }
};

// โ”€โ”€โ”€ Fix with weak_ptr (NO LEAK!) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
struct GoodNode {
    int val;
    std::shared_ptr<GoodNode> next;   // strong โ€” keeps next alive
    std::weak_ptr<GoodNode>   prev;   // weak โ€” doesn't prevent destruction!
    explicit GoodNode(int v) : val{v} { std::cout << "GoodNode " << v << " created
"; }
    ~GoodNode() { std::cout << "GoodNode " << val << " destroyed
"; }
};

// โ”€โ”€โ”€ Observer pattern with weak_ptr โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class EventEmitter;

class EventListener {
    std::string name_;
public:
    explicit EventListener(std::string name) : name_{std::move(name)} {}
    void onEvent(const std::string& event) {
        std::cout << name_ << " received: " << event << "
";
    }
    ~EventListener() { std::cout << name_ << " destroyed
"; }
};

class EventEmitter {
    std::vector<std::weak_ptr<EventListener>> listeners_;
public:
    void subscribe(std::weak_ptr<EventListener> listener) {
        listeners_.push_back(std::move(listener));
    }
    void emit(const std::string& event) {
        // Use lock() to safely access the listener
        auto it = listeners_.begin();
        while (it != listeners_.end()) {
            if (auto listener = it->lock()) {   // still alive?
                listener->onEvent(event);
                ++it;
            } else {
                std::cout << "(removing dead listener)
";
                it = listeners_.erase(it);      // auto-remove dead listeners!
            }
        }
    }
};

int main() {
    // CYCLE DEMO โ€” leak
    std::cout << "=== Cycle with shared_ptr (LEAK) ===
";
    {
        auto n1 = std::make_shared<BadNode>(1);
        auto n2 = std::make_shared<BadNode>(2);
        n1->next = n2;  // n1 holds n2
        n2->prev = n1;  // n2 holds n1 โ† CYCLE
        // n1 and n2 use_count = 2 each โ€” never reaches 0!
    }  // LEAK โ€” destructors never called!
    std::cout << "(should have seen 'destroyed' โ€” but didn't!)

";

    // FIX โ€” no leak
    std::cout << "=== Fix with weak_ptr (NO LEAK) ===
";
    {
        auto n1 = std::make_shared<GoodNode>(1);
        auto n2 = std::make_shared<GoodNode>(2);
        n1->next = n2;     // strong: n1โ†’n2
        n2->prev = n1;     // weak: n2 observes n1 (doesn't prevent destruction)
    }  // n1 destroyed (use_count 1โ†’0), then n2 โœ…

    // Observer pattern
    std::cout << "
=== Observer Pattern ===
";
    EventEmitter emitter;
    auto l1 = std::make_shared<EventListener>("Logger");
    auto l2 = std::make_shared<EventListener>("Analytics");
    emitter.subscribe(l1);
    emitter.subscribe(l2);
    emitter.emit("user_login");

    l2.reset();  // unsubscribe by destroying listener
    std::cout << "After l2 reset:
";
    emitter.emit("user_logout");  // Analytics auto-removed!
    return 0;
}
5Custom Deleters & Smart Pointer Passing Guidelines
C++ โ€” Custom deleters, file/socket managementโ–ถ Run in Compiler
#include <iostream>
#include <memory>
#include <cstdio>
#include <functional>

// โ”€โ”€โ”€ RAII file handle with custom deleter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
struct FileDeleter {
    void operator()(FILE* f) const {
        if (f) { std::fclose(f); std::cout << "FILE closed by custom deleter
"; }
    }
};

using FileHandle = std::unique_ptr<FILE, FileDeleter>;

FileHandle openRawFile(const char* path, const char* mode) {
    FILE* f = std::fopen(path, mode);
    if (!f) throw std::runtime_error(std::string("Cannot open: ") + path);
    return FileHandle{f};
}

// โ”€โ”€โ”€ Lambda deleter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
auto makeBuffer(std::size_t size) {
    return std::unique_ptr<char[], std::function<void(char*)>>(
        new char[size],
        [size](char* p) {
            std::cout << "Freeing buffer of " << size << " bytes
";
            delete[] p;
        }
    );
}

// โ”€โ”€โ”€ Passing smart pointers โ€” guideline table โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
void borrowObject(const std::string& s) {           // just borrows โ€” raw ref
    std::cout << "borrowing: " << s << "
";
}
void sinkObject(std::unique_ptr<std::string> s) {  // takes ownership
    std::cout << "sinking: " << *s << "
";
}  // destroyed here
void sharedAccess(std::shared_ptr<std::string> s) { // shares ownership
    std::cout << "shared: " << *s << " refcount=" << s.use_count() << "
";
}
void weakAccess(std::weak_ptr<std::string> w) {    // optional access
    if (auto p = w.lock()) std::cout << "weak: " << *p << "
";
    else std::cout << "object expired!
";
}

int main() {
    // Custom deleter for FILE
    try {
        auto f = openRawFile("test_output.txt", "w");
        std::fputs("Hello from smart FILE!
", f.get());
        // f automatically closed when scope ends
    } catch (const std::exception& e) {
        std::cout << "File error (OK if no permission): " << e.what() << "
";
    }

    // Lambda deleter
    auto buf = makeBuffer(256);
    buf[0] = 'A'; buf[1] = '';
    std::cout << "buf[0] = " << buf[0] << "
";

    // Passing patterns
    auto up = std::make_unique<std::string>("Hello");
    borrowObject(*up);                   // borrow by reference
    sinkObject(std::move(up));           // transfer ownership (up becomes null)
    if (!up) std::cout << "up is null after sink
";

    auto sp = std::make_shared<std::string>("World");
    sharedAccess(sp);                    // share (ref count increases temporarily)
    weakAccess(sp);                      // weak access while alive
    sp.reset();
    weakAccess(std::weak_ptr<std::string>{}); // expired!
    return 0;
}
6Rule of Zero & enable_shared_from_this
C++ โ€” Rule of Zero, enable_shared_from_thisโ–ถ Run in Compiler
#include <iostream>
#include <memory>
#include <vector>
#include <string>

// Rule of Zero: if you use smart pointers and STL containers to manage
// all resources, you don't need to write ANY of the 5 special members!
class SmartEmployee {
    std::string name_;
    std::vector<std::string> skills_;
    std::unique_ptr<std::string> biography_;  // unique resource
public:
    SmartEmployee(std::string name, std::string bio)
        : name_{std::move(name)}, biography_{std::make_unique<std::string>(std::move(bio))} {}

    void addSkill(std::string skill) { skills_.push_back(std::move(skill)); }
    void print() const {
        std::cout << "Employee: " << name_ << "
";
        std::cout << "Bio: " << *biography_ << "
";
        std::cout << "Skills: ";
        for (const auto& s : skills_) std::cout << s << " ";
        std::cout << "
";
    }
    // No need to write: destructor, copy/move constructors, copy/move assignment!
    // unique_ptr automatically makes this class move-only (non-copyable)
};

// enable_shared_from_this โ€” safe shared_ptr from within the object
class Worker : public std::enable_shared_from_this<Worker> {
    std::string task_;
public:
    explicit Worker(std::string task) : task_{std::move(task)} {}
    std::shared_ptr<Worker> getSelf() {
        return shared_from_this();  // safe โ€” returns shared_ptr to this
        // return std::shared_ptr<Worker>(this); // WRONG โ€” creates separate ownership!
    }
    void run() { std::cout << "Running task: " << task_ << "
"; }
    ~Worker() { std::cout << "Worker '" << task_ << "' done
"; }
};

int main() {
    // Rule of Zero demo
    SmartEmployee emp{"Alice", "Senior developer with 10 years experience"};
    emp.addSkill("C++20");
    emp.addSkill("RAII");
    emp.addSkill("Templates");
    emp.print();

    // enable_shared_from_this
    auto w1 = std::make_shared<Worker>("compile");
    auto w2 = w1->getSelf();  // both point to same Worker
    w1->run();
    std::cout << "Same object: " << (w1.get() == w2.get()) << "
";  // true
    std::cout << "use_count: " << w1.use_count() << "
";             // 2
    return 0;
}
7Technical FAQs

Q1: Why make_unique/make_shared instead of new?

Exception safety: f(unique_ptr<T>(new T), g()) could leak if g() throws between the new and smart pointer construction (pre-C++17). make_unique<T>() is atomic โ€” no such risk. Also cleaner and avoids repeating the type.

Q2: What is the overhead of shared_ptr vs unique_ptr?

unique_ptr has zero overhead โ€” same as raw pointer. shared_ptr carries a second pointer (control block with ref counts) and uses atomic increments/decrements for thread safety โ€” meaningful overhead in tight loops.

Q3: What is a cyclic reference and how does weak_ptr fix it?

When A holds shared_ptr<B> and B holds shared_ptr<A>, both ref-counts never reach 0 โ€” memory leaked forever. Break one direction with weak_ptr โ€” it observes without owning, allowing proper destruction.

Q4: Is shared_ptr thread-safe?

The ref-count management (copy/destruction of shared_ptr) is thread-safe. However, the pointed-to object is NOT protected โ€” concurrent access to the object still requires a mutex.

Q5: What is enable_shared_from_this?

When a member function needs to return a shared_ptr to itself (this), it can't call shared_ptr<T>(this) โ€” that creates a second independent ownership chain. Inheriting from enable_shared_from_this<T> and calling shared_from_this() safely returns a sharing copy of the existing shared_ptr.