Best examples of dynamic memory allocation in C++: new and delete

If you write modern C++, you still need to understand the old-school workhorse: dynamic memory management with `new` and `delete`. Even if you prefer smart pointers and STL containers, real-world codebases, interview questions, and embedded systems still lean heavily on classic dynamic allocation. In this guide, we’ll walk through clear, practical examples of dynamic memory allocation in C++: new and delete, and show how they behave in real programs. We’ll look at small, focused code snippets instead of vague theory. You’ll see how to allocate single objects, arrays, multidimensional data, and even custom resource managers. Along the way, we’ll contrast raw pointers with safer modern techniques so you can recognize legacy patterns, refactor them confidently, and avoid memory leaks. If you’ve ever stared at a `new` or `delete[]` in a code review and wondered, “Is this safe?”—this article is for you.
Written by
Jamie
Published
Updated

Let’s skip the textbook definitions and start with concrete code. These examples of dynamic memory allocation in C++: new and delete are the patterns you actually see in real projects, from legacy servers to embedded firmware.

Example 1: Allocating a single object with new and cleaning up with delete

A classic example of dynamic memory allocation in C++: new and delete is a single object that outlives the current scope.

#include <iostream>

struct User {
    int id;
    std::string name;
};

int main() {
    // Allocate a single User on the heap
    User* u = new User{42, "Ada"};

    std::cout << "User: " << u->id << ", " << u->name << '\n';

    // Release the memory
    delete u;
    u = nullptr; // avoid dangling pointer
}

You still see this pattern in older codebases where a factory function returns a raw pointer. In 2024–2025, you’d usually wrap this in std::unique_ptr<User> instead, but understanding this basic example of new and delete is mandatory if you maintain legacy C++.

Example 2: Dynamic arrays with new[] and delete[]

Another frequent example of dynamic memory allocation in C++: new and delete is a raw dynamic array. This is common in performance-sensitive or embedded code where STL containers may be restricted.

#include <iostream>

int main() {
    std::size_t n = 5;

    // Allocate an array of n ints on the heap
    int* data = new int[n];

    for (std::size_t i = 0; i < n; ++i) {
        data[i] = static_cast<int>(i * i); // 0, 1, 4, 9, 16
    }

    for (std::size_t i = 0; i < n; ++i) {
        std::cout << data[i] << ' ';
    }
    std::cout << '\n';

    // Must match new[] with delete[]
    delete[] data;
    data = nullptr;
}

Mixing new with delete[] or new[] with delete is undefined behavior. That’s one reason modern C++ code prefers std::vector<int> over raw arrays. But if you’re reading older code, this is one of the best examples to keep in mind.

Example 3: 2D dynamic array (matrix) using new and delete[]

Multidimensional data structures provide richer examples of dynamic memory allocation in C++: new and delete. Here’s a simple dynamic matrix:

#include <iostream>

int main() {
    std::size_t rows = 3;
    std::size_t cols = 4;

    // Allocate an array of row pointers
    int** matrix = new int*[rows];

    // Allocate each row
    for (std::size_t r = 0; r < rows; ++r) {
        matrix[r] = new int[cols];
    }

    // Initialize and print
    for (std::size_t r = 0; r < rows; ++r) {
        for (std::size_t c = 0; c < cols; ++c) {
            matrix[r][c] = static_cast<int>(r * cols + c);
            std::cout << matrix[r][c] << ' ';
        }
        std::cout << '\n';
    }

    // Free in reverse order of allocation
    for (std::size_t r = 0; r < rows; ++r) {
        delete[] matrix[r];
    }
    delete[] matrix;
}

This pattern shows up in numerical code and some older scientific libraries. In modern C++, you’d probably use a single std::vector<int> of size rows * cols or a library like Eigen or Blaze, but this is still a very realistic example of how dynamic memory was traditionally handled.

Example 4: Custom RAII wrapper for new / delete

Before smart pointers were standardized, many teams wrote their own RAII wrappers. This is a more advanced example of dynamic memory allocation in C++: new and delete, where you wrap raw pointers in a small class that manages lifetime.

#include <utility>

template <typename T>
class ScopedPtr {
public:
    explicit ScopedPtr(T* ptr = nullptr) noexcept : ptr_(ptr) {}

    ~ScopedPtr() {
        delete ptr_;
    }

    ScopedPtr(const ScopedPtr&) = delete;
    ScopedPtr& operator=(const ScopedPtr&) = delete;

    ScopedPtr(ScopedPtr&& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }

    ScopedPtr& operator=(ScopedPtr&& other) noexcept {
        if (this != &other) {
            delete ptr_;
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }

    T* get() const noexcept { return ptr_; }
    T& operator*() const { return *ptr_; }
    T* operator->() const noexcept { return ptr_; }

private:
    T* ptr_;
};

struct Connection {
    // ...
};

int main() {
    ScopedPtr<Connection> conn(new Connection{});
    // use conn like a pointer: conn->...
}

This is conceptually similar to std::unique_ptr<T>. Understanding this pattern helps you refactor legacy RAII wrappers into standard smart pointers during modernization.

Example 5: Factory function returning a raw pointer

Many older APIs return raw pointers from factory functions. That’s another common example of dynamic memory allocation in C++: new and delete.

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Circle : public Shape {
public:
    explicit Circle(double r) : r_(r) {}
    double area() const override { return 3.141592653589793 * r_ * r_; }
private:
    double r_;
};

Shape* make_circle(double radius) {
    return new Circle(radius); // caller owns the memory
}

int main() {
    Shape* s = make_circle(2.0);
    double a = s->area();

    // Later, caller must remember to free it
    delete s;
}

In fresh 2024–2025 code, you’d usually return std::unique_ptr<Shape> instead, but you’ll still see this pattern in many commercial codebases and interview questions.

Example 6: Dynamic memory in a small object pool

High-performance systems sometimes manage memory manually to avoid frequent allocations. Here’s a trimmed-down example of dynamic memory allocation in C++: new and delete used to build a tiny object pool.

#include <vector>
#include <stack>

struct Particle {
    float x, y, z;
};

class ParticlePool {
public:
    explicit ParticlePool(std::size_t capacity) {
        particles_.reserve(capacity);
        for (std::size_t i = 0; i < capacity; ++i) {
            particles_.push_back(new Particle{0.f, 0.f, 0.f});
            freeList_.push(particles_.back());
        }
    }

    ~ParticlePool() {
        while (!particles_.empty()) {
            delete particles_.back();
            particles_.pop_back();
        }
    }

    Particle* acquire() {
        if (freeList_.empty()) return nullptr; // pool exhausted
        Particle* p = freeList_.top();
        freeList_.pop();
        return p;
    }

    void release(Particle* p) {
        freeList_.push(p);
    }

private:
    std::vector<Particle*> particles_;
    std::stack<Particle*> freeList_;
};

Production-grade pools are more sophisticated, but this shows a realistic way teams use multiple new calls up front, then recycle objects instead of allocating and freeing them constantly.

Example 7: Interop with C APIs that expect raw pointers

Even in 2025, plenty of C libraries require raw pointers and manual memory handling. Here’s an example of dynamic memory allocation in C++: new and delete in the context of C-style APIs.

extern "C" void c_api_process_buffer(char* buffer, int length);

void process_with_c_api(int length) {
    char* buf = new char[length];

    // initialize buffer
    for (int i = 0; i < length; ++i) {
        buf[i] = static_cast<char>('A' + (i % 26));
    }

    c_api_process_buffer(buf, length);

    delete[] buf; // must be delete[], not delete
}

When wrapping a C library in a C++ interface, this is one of the best examples of how new / delete[] still show up in serious, modern code.

If you’re following current C++ guidelines from the C++ Core Guidelines project or educational material from universities like MIT or Stanford, you’ll notice a clear trend: avoid naked new and delete in fresh code whenever possible.

Instead, modern C++ favors:

  • std::unique_ptr and std::shared_ptr for object lifetimes
  • std::vector, std::string, and other containers for dynamic storage
  • Custom allocators and memory resource (std::pmr) for advanced performance tuning

So why bother with examples of dynamic memory allocation in C++: new and delete at all?

Because you still need to:

  • Read and maintain large legacy systems where raw pointers are everywhere
  • Pass data to and from C APIs that have no idea what a smart pointer is
  • Understand the underlying model when debugging memory leaks, double frees, or use-after-free bugs

Tools like AddressSanitizer (ASan) and LeakSanitizer (LSan), widely used in 2024–2025 in CI pipelines, make it much easier to catch misuse of new and delete. But those tools work best when you already understand what correct usage looks like.

Authoritative sources such as the C++ Core Guidelines (maintained with input from ISO C++ committee members) strongly encourage RAII and resource ownership semantics that build on top of new and delete, not random manual calls scattered across the codebase.

Safer patterns that still rely on dynamic allocation under the hood

To round out these examples of dynamic memory allocation in C++: new and delete, it helps to see how modern idioms hide raw pointers while still using the heap.

Smart pointer example using std::unique_ptr

#include <memory>

struct Session {
    // ...
};

std::unique_ptr<Session> make_session() {
    // new is still called, but wrapped in a factory
    return std::make_unique<Session>();
}

int main() {
    auto session = make_session();
    // No explicit delete needed; RAII handles cleanup
}

Internally, std::make_unique uses new to allocate the Session. The difference is that ownership is explicit, and delete is guaranteed when the unique_ptr goes out of scope.

Container example using std::vector

#include <vector>

int main() {
    std::vector<int> values;
    values.reserve(1000); // may cause a heap allocation

    for (int i = 0; i < 1000; ++i) {
        values.push_back(i);
    }
}

std::vector allocates memory dynamically, but you never see new or delete directly. Understanding the earlier raw-pointer examples makes it easier to reason about what vector is doing behind the scenes, especially when you debug performance or memory usage.

Common mistakes when using new and delete

Looking at real examples of dynamic memory allocation in C++: new and delete also means talking about how they go wrong. A few classic pitfalls:

  • Forgetting to call delete or delete[] → memory leak
  • Calling delete twice on the same pointer → double free
  • Using delete where delete[] is required (or vice versa) → undefined behavior
  • Using a pointer after it’s been deleted → use-after-free
  • Throwing exceptions between new and delete without RAII → leaked memory

Modern coding standards strongly recommend:

  • Immediately wrapping raw new allocations in a smart pointer
  • Initializing pointers to nullptr and resetting them after delete
  • Using RAII objects so cleanup happens automatically

These habits keep your examples of dynamic memory allocation in C++: new and delete from turning into late-night debugging sessions.


FAQ: common questions about new, delete, and real examples

What is a simple example of dynamic memory allocation with new in C++?

A simple example of dynamic memory allocation in C++ is:

int* p = new int(10);
// use *p
delete p;

This allocates an int on the heap, initializes it to 10, and then frees it.

What are typical real examples of using new[] and delete[]?

Typical real examples include dynamically sized arrays when the size isn’t known at compile time, such as buffers for I/O, image data, or numerical grids:

std::size_t n = get_runtime_size();
char* buffer = new char[n];
// ... use buffer ...
delete[] buffer;

Should I still use new and delete in new C++ code?

In most new code, you should avoid naked new and delete. Prefer std::unique_ptr, std::shared_ptr, and standard containers. However, you must understand these examples of dynamic memory allocation in C++: new and delete to work effectively with legacy systems, embedded environments, or low-level libraries.

How do I debug memory leaks from new / delete?

Use tools like AddressSanitizer and LeakSanitizer, or platform-specific tools such as Visual Studio’s built-in diagnostics. These tools track allocations and deallocations and report leaks or invalid frees. The patterns you saw in the code examples above map directly to what these tools report.

What’s the difference between new and malloc?

new constructs C++ objects and calls constructors, while malloc just reserves raw memory and returns void*. With malloc, you must cast and manually run constructors if needed. In idiomatic C++, new / delete (or better, RAII wrappers) are preferred.


The bottom line: you don’t have to love raw pointers, but you do need to recognize and reason about examples of dynamic memory allocation in C++: new and delete. That’s how you safely maintain older systems, wrap C libraries, and understand what your higher-level abstractions are doing under the hood.

Explore More C++ Code Snippets

Discover more examples and insights in this category.

View All C++ Code Snippets