New and delete in C++: Dynamic Memory Allocation

Mastering New and delete in C++ gives control over memory lifetimes and sizes at runtime. This article explains single-object and array allocation, error handling, best practices, and safe patterns— with clear examples and challenges to build confidence.

🔹 Why Dynamic Memory (Runtime Allocation)?

Dynamic allocation is used when the size or lifetime of data isn’t known at compile time—e.g., reading N items from input, or managing objects that outlive the current scope. new allocates memory and constructs objects; delete destroys and frees them. For arrays, use new[] and delete[].

1. new/delete for a Single Object

Allocate with new T(...) and free with delete. Always set pointers to nullptr after deletion to avoid dangling pointers.

#include <iostream>
int main() {
    int* p = new int(42);   // allocate and initialize
    std::cout << "*p: " << *p << "\n";
    delete p;               // free
    p = nullptr;            // avoid dangling
}

Output

*p: 42

📝 Try it Yourself: Dynamically allocate a double, initialize it to 3.14, print it, then delete it and set the pointer to nullptr.

2. new[] / delete[] for Arrays

Arrays require new[] and must be freed with delete[]. Never mix new with delete[] or new[] with delete.

#include <iostream>
int main() {
    int n = 5;
    int* arr = new int[n]{1,2,3,4,5}; // allocate array
    for (int i = 0; i < n; ++i) std::cout << arr[i] << " ";
    std::cout << "\n";
    delete[] arr;          // correct matching delete[]
    arr = nullptr;
}

Output

1 2 3 4 5

📝 Try it Yourself: Read n from input, allocate an array of n ints, fill it with i*i, print values, then free it with delete[].

3. Handling Allocation Failure (std::bad_alloc)

By default, new throws std::bad_alloc if allocation fails. Use try/catch to handle errors gracefully. Alternatively, use nothrow to get nullptr instead of an exception.

3.1 Exception form (throws std::bad_alloc)

#include <iostream>
#include <new>
int main() {
    try {
        std::size_t big = static_cast<std::size_t>(1) << 40;
        int* huge = new int[big];
        delete[] huge;
    } catch (const std::bad_alloc&) {
        std::cout << "Allocation failed!\n";
    }
}

Output (example)

Allocation failed!

3.2 Nothrow form (returns nullptr)

#include <iostream>
#include <new>
int main() {
    std::size_t big = static_cast<std::size_t>(1) << 40;
    int* huge = new (std::nothrow) int[big];
    if (!huge) {
        std::cout << "Allocation returned nullptr\n";
        return 0;
    }
    delete[] huge;
}

📝 Try it Yourself: Switch between exception and nothrow forms; log which path you hit and verify you always free memory when allocation succeeds.

4. Objects, Constructors, and Destructors

new constructs objects; delete calls the destructor and frees memory. For arrays of objects, use new[]/delete[] to ensure all destructors run.

#include <iostream>
struct Widget {
    Widget()  { std::cout << "Widget constructed\n"; }
    ~Widget() { std::cout << "Widget destroyed\n"; }
};
int main() {
    Widget* w  = new Widget;     // single object
    delete w;
    Widget* wa = new Widget[2];  // array of objects
    delete[] wa;
}

Output (example)

Widget constructed
Widget destroyed
Widget constructed
Widget constructed
Widget destroyed
Widget destroyed

5. Best Practices (RAII and Smart Pointers)

Prefer RAII: resource acquisition is initialization. In modern C++, use smart pointers (std::unique_ptr, std::shared_ptr) to manage ownership automatically. They call delete for you.

5.1 unique_ptr for Single Objects

#include <memory>
#include <iostream>
int main() {
    auto p = std::make_unique<int>(123);
    std::cout << *p << "\n"; // auto-freed when out of scope
}

5.2 unique_ptr for Arrays

#include <memory>
#include <iostream>
int main() {
    std::unique_ptr<int[]> arr(new int[3]{10,20,30});
    for (int i = 0; i < 3; ++i) std::cout << arr[i] << " ";
    std::cout << "\n"; // auto-freed
}

📝 Try it Yourself: Replace a raw new/delete example with std::unique_ptr and confirm no manual delete is needed.

6. Common Pitfalls to Avoid

  • Mismatched delete: Use delete for new and delete[] for new[]. Mixing them is undefined behavior.
  • Memory leaks: Forgetting delete/delete[] leaks memory. Prefer RAII and smart pointers to prevent leaks.
  • Dangling pointers: Don’t use a pointer after deleting it. Set it to nullptr immediately.
  • Unnecessary dynamic allocation: Prefer automatic storage or containers like std::vector unless dynamic allocation is truly needed.

🔹 Frequently Asked Questions (FAQ)

Q: What happens if I use delete instead of delete[]?
A: It’s undefined behavior for arrays; not all destructors may run, and memory can be corrupted or leaked.

Q: Do I always need dynamic memory?
A: No. Prefer stack variables and standard containers (std::vector, std::string, etc.). Use dynamic allocation only when lifetimes or sizes must be decided at runtime.

Q: Is new/delete still used in modern C++?
A: Yes, but idiomatic modern C++ wraps ownership in RAII (smart pointers/containers), minimizing raw new/delete in application code.

Leave a Comment

About RadiantRiva

Your go-to resource for coding tutorials, developer guides, and programming tips.

Learn More

Quick Links

Follow Us

Newsletter

Get coding tips, tutorials, and updates straight to your inbox.