Smart Pointers: unique, shared, weak

Smart pointer-এ গভীর জ্ঞান

Read: ~40 min 16 practice problems

1. The Three Smart Pointers

TypeOwnershipCost
std::unique_ptr<T>Single owner~Zero (same as raw pointer)
std::shared_ptr<T>Multiple owners (reference counted)Atomic increment + control block
std::weak_ptr<T>Non-owning observer (breaks cycles)Like shared, but no ref count

2. unique_ptr — Default Choice

unique.cpp
#include <iostream>
#include <memory>

struct Widget {
    int id;
    Widget(int i) : id(i) { std::cout << "Widget " << id << " born\n"; }
    ~Widget() { std::cout << "Widget " << id << " dies\n"; }
};

void use(const Widget& w) { std::cout << "using " << w.id << "\n"; }

int main() {
    auto p = std::make_unique<Widget>(42);
    use(*p);
    auto p2 = std::move(p);     // transfer ownership
    // p is now nullptr
}

3. shared_ptr — When You Need Sharing

shared.cpp
#include <iostream>
#include <memory>

int main() {
    auto p1 = std::make_shared<int>(42);
    {
        auto p2 = p1;
        std::cout << "count = " << p1.use_count() << "\n"; // 2
    }
    std::cout << "count = " << p1.use_count() << "\n"; // 1
}
Use shared_ptr only when ownership is truly shared For most cases, unique_ptr + raw pointer (non-owning) is the right design.

4. weak_ptr — Breaking Cycles

If two shared_ptrs reference each other (a cycle), the count never reaches zero → leak. weak_ptr doesn't increment the count.

weak.cpp
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node>   prev;  // don't own backwards
};

// Use:
if (auto sp = node->prev.lock()) {
    // sp is shared_ptr while alive
}

5. Custom Deleters

For non-memory resources (files, sockets):

deleter.cpp
#include <cstdio>
#include <memory>

std::unique_ptr<FILE, decltype(&std::fclose)>
    f(std::fopen("data.txt", "r"), &std::fclose);
// auto-closes on scope exit

6. Decision Guide

Which to choose?
  1. Sole owner → unique_ptr (default)
  2. Truly shared → shared_ptr (audit your design first!)
  3. Observer (cache, parent ref) → raw pointer or weak_ptr
  4. Owns a resource that needs cleanup → smart ptr with custom deleter

7. Practice Problems

  1. Create a unique_ptr<int> with value 7.
    ✨ Show Answer
    auto p = std::make_unique<int>(7);
  2. Why prefer make_unique?
    ✨ Show Answer

    Exception safety + concise. std::unique_ptr<T>(new T) can leak in f(unique_ptr(new T), g()) if g throws.

  3. Transfer unique_ptr to a function.
    ✨ Show Answer
    void take(std::unique_ptr<T> p);
    take(std::move(my_unique));
  4. Pass a unique_ptr to a function that doesn't own it.
    ✨ Show Answer
    void use(const T& obj); // pass *p

    Or pass a raw pointer if it can be null. Don't take a unique_ptr by reference.

  5. Why is shared_ptr more expensive?
    ✨ Show Answer

    Atomic ref count increment/decrement on copy/destroy. Plus an extra control block allocation (mostly avoided by make_shared).

  6. When does shared_ptr leak?
    ✨ Show Answer

    Reference cycles. Two shared_ptrs holding each other never reach 0. Break with weak_ptr.

  7. Get a shared_ptr's count.
    ✨ Show Answer
    p.use_count();
  8. Lock a weak_ptr to use the object.
    ✨ Show Answer
    if (auto sp = wp.lock()) {
        use(*sp);
    }
  9. Create a shared_ptr with a custom deleter.
    ✨ Show Answer
    std::shared_ptr<FILE> f(std::fopen("x", "r"), [](FILE* p){ std::fclose(p); });
  10. When is unique_ptr essentially free?
    ✨ Show Answer

    Always. It's a wrapper around a single pointer with a destructor. No runtime overhead vs raw pointer.

  11. Convert unique_ptr to shared_ptr.
    ✨ Show Answer
    std::shared_ptr<T> sp = std::move(up);
  12. Reset a unique_ptr to nullptr.
    ✨ Show Answer
    p.reset(); // or p = nullptr;
  13. Get raw pointer (non-owning) from a unique_ptr.
    ✨ Show Answer
    T* raw = p.get();
  14. Why might shared_ptr<T> have a lower performance ceiling than unique_ptr?
    ✨ Show Answer

    Atomic operations on the count are expensive in multi-threaded code. They limit scaling.

  15. Use std::enable_shared_from_this — why?
    ✨ Show Answer

    Lets a class create a shared_ptr to itself from a member function (shared_from_this()) without creating a separate, unrelated control block.

  16. vector<unique_ptr<T>> — what does it model?
    ✨ Show Answer

    A collection that owns polymorphic objects. Common pattern for storing different derived types.

Summary

Default to unique_ptr; it's free. Use shared_ptr only when ownership is genuinely shared. weak_ptr breaks cycles. Use make_unique / make_shared rather than raw new. Custom deleters extend smart pointers to any resource.

Next Module → Exception Handling.