BIT153 Object Oriented Programming

Object Oriented ProgrammingUnit 99 min read

Dynamic Memory Allocation & Smart Pointers in C++: new/delete, RAII, and Memory Leaks

Unit 9 of Object Oriented Programming explores how C++ manages memory at runtime using new/delete, smart pointers (uniqueptr, sharedptr), and RAII principles—critical for building scalable apps like eSewa’s transaction logs or Pathao’s ride-matching queues.

TAKEAWAYS:

  • Dynamic allocation (new/delete) creates objects at runtime but risks memory leaks if mismanaged.
  • Smart pointers (unique_ptr, shared_ptr) automate cleanup via RAII, preventing leaks in modern C++.
  • RAII (Resource Acquisition Is Initialization) ties resource lifetime to object scope, used in every C++ library.
  • new[]/delete[] handle arrays dynamically, but mismatched calls cause undefined behavior.
  • Custom allocators (e.g., std::pmr) optimize memory for high-performance systems like game engines.
  • Valgrind (Linux) or Visual Studio’s Debugger detects leaks in real-world projects.

Core Concepts: How Memory Works in C++

1. Static vs. Dynamic Memory

C++ allocates memory in two ways:

  • Static memory: Fixed at compile-time (global variables, local static variables).
  • Dynamic memory: Allocated at runtime using new/delete (or malloc/free in C-style).
classDiagram
    class Memory {
        +static: Fixed size, compile-time
        +dynamic: Variable size, runtime via `new`
    }
    class Allocation {
        +new: Allocates single object
        +new[]: Allocates array
        +delete: Frees single object
        +delete[]: Frees array
    }
    Memory --> Allocation : "Uses"

Why dynamic memory?

  • Flexibility: Create objects only when needed (e.g., Pathao’s ride queue grows/shrinks).
  • Efficiency: Avoid wasting memory for rarely used objects (e.g., Daraz’s order history).

2. The new and delete Operators

Allocation
int* ptr = new int(42);  // Allocates memory for an int initialized to 42

What happens?

flowchart LR
    A["new int(42)"] --> B["Heap: [42]"]
    B --> C["ptr → Heap[0]"]
Deallocation
delete ptr;  // Frees memory

State after delete:

flowchart LR
    A["ptr → NULL"] --> B["Heap: [FREED]"]

Key Rules:

  • Always pair new with delete (or use smart pointers).
  • Never delete the same pointer twice (crash!).
  • Never delete a pointer returned by new[] with delete (memory corruption).

In the Real World

  1. eSewa’s Transaction Logs

    • Uses dynamic arrays (new[]) to store thousands of payment records.
    • Why? Static arrays can’t grow; dynamic arrays scale with user transactions.
    • Risk: Forgetting delete[] leaks memory, crashing the system during peak hours.
  2. Pathao’s Ride-Matching Queue

    • Dynamically allocates Driver and Passenger objects for real-time matching.
    • Optimization: Uses std::vector (dynamic array) to avoid manual new/delete.
  3. Ncell’s Call Routing

    • Smart pointers (shared_ptr) manage network connection objects.
    • Why? Multiple threads share call data; shared_ptr ensures cleanup when all threads finish.

3. Memory Leaks: The Silent Killer

A memory leak occurs when allocated memory is no longer accessible (e.g., lost pointer). Example: Lost Pointer

void leak() {
    int* ptr = new int(100);
    ptr = new int(200);  // Original 100 is lost!
}

Visualization:

flowchart TD
    A["ptr = new int(100)"] --> B["Heap: [100]"]
    B --> C["ptr = new int(200)"]
    C --> D["Heap: [100 (LEAKED)], [200]"]

How to Detect?

  • Valgrind (Linux):
    valgrind --leak-check=full ./your_program
    
  • Visual Studio Debugger: Enable "Detect Memory Leaks" in project settings.

4. Smart Pointers: Automatic Cleanup

Smart pointers manage memory automatically using RAII (Resource Acquisition Is Initialization).

Types of Smart Pointers
Pointer Type Header Use Case Example
std::unique_ptr <memory> Exclusive ownership auto ptr = std::make_unique<int>(42);
std::shared_ptr <memory> Shared ownership (reference counting) auto ptr = std::make_shared<int>(42);
std::weak_ptr <memory> Break circular references std::weak_ptr<int> weak = ptr;

Example: unique_ptr

#include <memory>
void safeAllocation() {
    auto ptr = std::make_unique<int>(100);  // Automatically deleted at scope end
    // No need for manual delete!
}

State after scope ends:

flowchart LR
    A["ptr goes out of scope"] --> B["Heap: [FREED]"]
shared_ptr and Reference Counting
auto ptr1 = std::make_shared<int>(50);
auto ptr2 = ptr1;  // Reference count = 2

Visualization:

flowchart TD
    A["ptr1 → Heap[50]"] --> B["ptr2 → Heap[50]"]
    B --> C["Ref Count: 2"]

When shared_ptr deletes:

flowchart LR
    A["ptr1 and ptr2 go out of scope"] --> B["Ref Count: 0 → Heap[FREED]"]

5. Dynamic Arrays: new[] and delete[]

Allocation
int* arr = new int[5];  // Allocates 5 ints
arr[0] = 10; arr[1] = 20;

Heap State:

flowchart TD
    A["new int[5]"] --> B["Heap: [10, 20, 0, 0, 0]"]
Deallocation
delete[] arr;  // Must use delete[], not delete!

Warning:

delete[] arr;  // Correct
delete arr;    // CRASH: Undefined behavior!

6. RAII: The C++ Way to Manage Resources

RAII Principle:

"Tie a resource’s lifetime to an object’s lifetime."

Example: File Handling with RAII

#include <fstream>
void safeFileWrite() {
    std::ofstream file("data.txt");  // Opens file
    file << "Hello, RAII!";          // Writes data
}  // File automatically closed here!

Why RAII?

  • Prevents leaks (e.g., forgotten file.close()).
  • Works for all resources: files, sockets, locks.

7. Custom Allocators and std::pmr

For high-performance apps (e.g., game engines), use polymorphic allocators:

#include <memory_resource>
std::pmr::memory_resource* mr = std::pmr::get_default_resource();
auto ptr = std::pmr::make_unique<int>(mr, 42);

Use Case:

  • Unreal Engine: Uses custom allocators for memory pools.
  • NEPSE’s Trading System: Optimizes memory for high-frequency trades.

Worked Example: Bank Loan Calculator with Dynamic Memory

Problem: A bank allocates loan objects dynamically. Calculate interest for 3 loans. Solution:

#include <iostream>
#include <memory>

class Loan {
public:
    Loan(double amount, double rate) : amount(amount), rate(rate) {}
    double calculateInterest(int years) {
        return amount * rate * years;
    }
private:
    double amount, rate;
};

int main() {
    // Dynamic allocation with unique_ptr
    auto loan1 = std::make_unique<Loan>(100000, 0.05);
    auto loan2 = std::make_unique<Loan>(500000, 0.07);
    auto loan3 = std::make_unique<Loan>(200000, 0.06);

    std::cout << "Interest for Loan 1: "
              << loan1->calculateInterest(5) << std::endl;
    // Output: 25000
    return 0;
}

Heap State After Allocation:

flowchart TD
    A["loan1 → Loan(100k, 5%)"] --> B["loan2 → Loan(500k, 7%)"]
    B --> C["loan3 → Loan(200k, 6%)"]

Why This Matters for Banks:

  • Scalability: Loans are created/destroyed as customers join/leave.
  • Safety: unique_ptr ensures no memory leaks in high-stakes systems.

Common Pitfalls and How to Avoid Them

Pitfall Solution Example
Forgetting delete Use smart pointers (unique_ptr) auto ptr = std::make_unique<int>(42);
Mismatched new[]/delete Always use delete[] for arrays delete[] arr;
Dangling pointers Reset pointers to nullptr after delete delete ptr; ptr = nullptr;
Circular references in shared_ptr Use weak_ptr to break cycles std::weak_ptr<int> weak = ptr;

Exam Tip

  1. Always explain the difference between new/delete and smart pointers in answers.
    • "new requires manual cleanup; unique_ptr automates it via RAII."
  2. Draw heap diagrams for dynamic allocation questions (e.g., show state before/after delete).
  3. Mention RAII whenever resources are involved (files, memory, locks).
  4. For polymorphism questions, link to dynamic binding (e.g., virtual functions use dynamic memory).
  5. Practice tracing:
    • Start with a raw pointer example, then rewrite it with shared_ptr.
    • Show how reference counts change.

Quick Revision Table

Concept Syntax Example Key Point
Dynamic Allocation int* ptr = new int(10); Use delete to free.
Dynamic Array int* arr = new int[5]; Use delete[] to free.
unique_ptr auto ptr = std::make_unique<int>(10); Exclusive ownership.
shared_ptr auto ptr = std::make_shared<int>(10); Reference counting.
RAII std::ofstream file("data.txt"); Resource tied to object lifetime.
Memory Leak new int; // No delete Use Valgrind to detect.

Final Challenge: Debug the Leak

void buggyFunction() {
    int* arr = new int[10];
    arr[0] = 100;
    // Missing delete[]!
}

Fix:

void fixedFunction() {
    auto arr = std::make_unique<int[]>(10);  // Automatically freed
    arr[0] = 100;
}

Heap After Fix:

flowchart LR
    A["arr goes out of scope"] --> B["Heap: [FREED]"]

Based on the TU BIT syllabus for Object Oriented Programming (BIT153), unit 9.

Discussion

Loading…