Dynamic Memory: new/delete vs Smart Pointers

ডায়নামিক মেমরি — heap ও RAII

Read: ~35 min 18 practice problems

1. Stack vs Heap

AspectStackHeap
LifetimeUntil end of scopeUntil you free it
SpeedVery fast (just bump SP)Slower (allocator work)
SizeSmall (~MB)Huge (limited by OS)
AllocateAutomaticnew / malloc
FreeAutomaticdelete / free

2. new and delete — The Old Way

new.cpp
#include <iostream>

int main() {
    int* p = new int(42);     // allocate one int on heap
    std::cout << *p << "\n";
    delete p;                  // MUST free

    int* arr = new int[5]{1,2,3,4,5};
    delete[] arr;              // note: delete[]
}
The classic 4 sins
  1. Forget to delete → memory leak
  2. delete twice → undefined behavior (double-free)
  3. Use after delete → undefined behavior (use-after-free)
  4. delete instead of delete[] → undefined behavior

3. std::unique_ptr — The Modern Way

Owns one heap object. Deletes it when the smart pointer goes out of scope.

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

struct Widget {
    Widget()  { std::cout << "created\n"; }
    ~Widget() { std::cout << "destroyed\n"; }
};

int main() {
    auto w = std::make_unique<Widget>();
    std::cout << "using widget...\n";
    // no delete — auto cleanup at scope end
}

4. RAII — The Big Idea

Resource Acquisition Is Initialization: tie a resource (memory, file handle, lock) to an object's lifetime. The destructor releases it. Exception thrown? Stack unwinds, destructors run, no leak.

RAII হলো C++-এর সবচেয়ে গুরুত্বপূর্ণ idiom। যেকোনো resource — memory, file, lock — object-এর lifetime-এর সাথে বাঁধা থাকবে। Object destroy হলেই resource স্বয়ংক্রিয়ভাবে release হবে।

5. Memory Tools

  • AddressSanitizer (ASan): g++ -fsanitize=address. Catches leaks, use-after-free, double-free.
  • Valgrind: valgrind ./myapp. Linux-only, slow but thorough.
  • UndefinedBehaviorSanitizer: -fsanitize=undefined. Catches signed overflow, null deref, etc.

6. Practice Problems

  1. Allocate an int=99 with new. Print and delete.
    ✨ Show Answer
    int* p = new int(99);
    std::cout << *p; delete p;
  2. Same with unique_ptr.
    ✨ Show Answer
    auto p = std::make_unique<int>(99);
    std::cout << *p; // auto-deletes
  3. What's a "memory leak"?
    ✨ Show Answer

    Heap memory you allocated but never freed. The OS reclaims it when the process exits, but a long-running server slowly bloats and crashes.

  4. Why use make_unique instead of new?
    ✨ Show Answer

    Exception safety: in f(unique_ptr<T>(new T), g()), if g() throws between new and the constructor of unique_ptr, you leak. make_unique avoids this.

  5. Allocate an array of 100 doubles.
    ✨ Show Answer
    auto arr = std::make_unique<double[]>(100);
  6. Pass unique_ptr to a function — what happens?
    ✨ Show Answer

    Cannot copy. Either pass by value with std::move (transfers ownership), or by reference for non-owning use.

  7. Difference between delete p and delete[] p?
    ✨ Show Answer

    delete calls one destructor and frees a single object. delete[] calls each element's destructor and frees an array. Mixing them is UB.

  8. Show why double-delete is bad.
    ✨ Show Answer
    int* p = new int;
    delete p;
    delete p; // UB: heap corruption likely
  9. Use ASan to find leaks: command line?
    ✨ Show Answer
    g++ -fsanitize=address -g app.cpp -o app
    ./app

    ASan prints leaks at exit.

  10. Why is RAII the central idea of C++?
    ✨ Show Answer

    Resource cleanup happens automatically when objects go out of scope, even on exceptions. No manual cleanup, no leaks. It's how modern C++ achieves safety without GC.

  11. Stack or heap: int x = 5;
    ✨ Show Answer

    Stack (automatic).

  12. Stack or heap: int* p = new int(5);
    ✨ Show Answer

    The pointer p is on the stack; the int it points to is on the heap.

  13. Why is heap allocation slower than stack?
    ✨ Show Answer

    Stack alloc = adjust SP register (~1 cycle). Heap alloc = traverse free list, possibly request memory from OS, possibly take a lock — orders of magnitude slower.

  14. When MUST you use new/delete directly?
    ✨ Show Answer

    Almost never. The only legitimate cases: implementing your own container, custom allocator, or interop with a C API that frees with the matching primitive.

  15. What if a constructor throws after partial allocation?
    ✨ Show Answer

    Already-constructed members are destroyed (RAII). The object is never "partially" alive. new T(...) deallocates if T's constructor throws. Smart pointers leverage this.

  16. What does nullptr_t have to do with smart pointers?
    ✨ Show Answer

    You can compare smart pointers to nullptr: if (!p) ... means "if not yet owning anything".

  17. Demonstrate exception safety with unique_ptr.
    ✨ Show Answer
    auto p = std::make_unique<Widget>();
    throw std::runtime_error{"oops"};
    // Widget still gets destroyed during stack unwind!
  18. Can a unique_ptr be copied?
    ✨ Show Answer

    No. Copying would mean two owners — violates the "sole owner" invariant. It can be moved (transferring ownership).

Summary

Modern C++ avoids raw new/delete. Use std::make_unique for sole ownership and std::make_shared for shared ownership (Module 32). RAII makes leaks impossible — destructors run automatically, even on exceptions. Compile with -fsanitize=address to catch any remaining bugs.

Next Module → Structs, Tuples & std::pair.