C++ Lesson 28 – Dynamic Allocation | Dataplexa
Advanced C++ Β· Lesson 28

Dynamic Allocation

Lesson 27 gave you new and delete for a single value. But real programs rarely know their sizes in advance β€” how many scores will the user enter, how many books are in the file, how many players join the game. In this lesson you will allocate whole arrays and whole objects while the program runs, grow an array when it fills up, and learn the one bracket rule that keeps it all safe.

1. Why Allocate at Run Time?

A normal array like int scores[10]; has its size fixed when you write the code β€” ten slots, no more, no less. If the user has only 4 scores, 6 slots sit empty. If the user has 40, you are out of room. Dynamic allocation fixes this: you ask for exactly as much memory as you need, at the moment you need it, and you give it back when you are done.

Code story: A teacher has 4 exam scores to average. She does not want a fixed-size array that wastes space, so the program asks the heap for an array of exactly 4 integers, fills it with the scores, adds them up, and prints the average. Watch for the new int[n] line β€” that is the moment the array is born β€” and for the delete[] line at the end, which returns the memory.

#include <iostream>
using namespace std;

int main() {
    int n = 4; // in a real program, read this with cin
    int* scores = new int[n]{90, 85, 78, 92};
    int total = 0;
    for (int i = 0; i < n; i++) {
        total += scores[i];
    }
    cout << "Scores:";
    for (int i = 0; i < n; i++) {
        cout << " " << scores[i];
    }
    cout << endl;
    cout << "Average: " << total / (double)n << endl;
    delete[] scores;
    return 0;
}
Scores: 90 85 78 92
Average: 86.25

What just happened? The line new int[n] asked the heap for room for 4 integers and gave back a pointer to the first one. After that, scores[i] works exactly like a normal array β€” the only difference is where the memory lives. The (double)n in the average line is the cast from Lesson 7: without it, 345 / 4 would give 86, not 86.25. And delete[] returns the whole array β€” the brackets say this was an array, not a single value.

2. new[] and delete[] β€” Arrays on the Heap

There are two pairs of tools, and they must never be mixed. new goes with delete for one value. new[] goes with delete[] for an array. The brackets are not decoration β€” they tell the program how much memory to free.

AllocationFree it withWhat happens
new intdeleteFrees one value β€” the correct pair.
new int[n]delete[]Frees the whole array β€” the correct pair.
new int[n]delete (no brackets)Undefined behaviour β€” only the first element is freed.
new intdelete[]Undefined behaviour β€” the wrong form for a single value.

The rule to remember: brackets in, brackets out. If the allocation has [], the freeing must have [] too. If the allocation has no brackets, the freeing has none either.

3. Allocating Objects at Run Time

You can allocate whole objects the same way. new Car("Swift") runs the constructor, gives you a pointer, and delete runs the destructor. The object lives on the heap, so it survives after the function that made it returns β€” only delete ends its life.

Code story: A car is built on the heap. The moment new Car("Swift") runs, the constructor announces the car. Then the car starts. Then delete runs the destructor and the car is gone. Watch the order: created, started, destroyed β€” exactly the life of the object.

#include <iostream>
#include <string>
using namespace std;

class Car {
private:
    string name;
public:
    Car(string n) : name(n) {
        cout << "Car " << name << " created." << endl;
    }
    void start() {
        cout << name << " is starting." << endl;
    }
    ~Car() {
        cout << "Car " << name << " destroyed." << endl;
    }
};

int main() {
    Car* c = new Car("Swift");
    c->start();
    delete c;
    return 0;
}
Car Swift created.
Swift is starting.
Car Swift destroyed.

What just happened? The new keyword did two jobs at once: it reserved heap memory for a Car, and it ran the constructor with "Swift". The arrow -> calls a member through a pointer β€” it is the pointer version of the dot. Then delete c; ran the destructor first and freed the memory second. If you forget the delete, the destructor never runs and the memory leaks.

4. Dynamic Arrays of Objects

An array of objects works the same way, with one extra rule: new Book[3] must build 3 books at once, so the class needs a constructor with no arguments β€” a default constructor. Each element is then filled in, used, and torn down by delete[], which runs the destructor for every single element.

Code story: A bookshelf holds 3 books, but the shelf itself is built on the heap. First the array is created β€” all 3 books start as Untitled, because that is what the default constructor does. Then each slot is replaced with a real book. Watch for the moment shelf[i].print() is called β€” the dot works here because shelf[i] is an actual Book, not a pointer.

#include <iostream>
#include <string>
using namespace std;

class Book {
private:
    string title;
public:
    Book() : title("Untitled") {}
    Book(string t) : title(t) {}
    void print() {
        cout << "Book: " << title << endl;
    }
};

int main() {
    Book* shelf = new Book[3];
    shelf[0] = Book("Dune");
    shelf[1] = Book("C++ Primer");
    shelf[2] = Book("The Hobbit");
    for (int i = 0; i < 3; i++) {
        shelf[i].print();
    }
    delete[] shelf;
    return 0;
}
Book: Dune
Book: C++ Primer
Book: The Hobbit

What just happened? new Book[3] built 3 books with the default constructor, so all 3 started as Untitled. The three assignment lines then replaced each one with a real book. The loop prints them one by one, and delete[] shelf; destroys all 3 and frees the array. If the class had no default constructor, this code would not compile at all β€” that is the compiler protecting you.

5. Growing an Array β€” When the Size Changes

A heap array cannot grow in place β€” its size is set when you allocate it. So when you need more room, you follow four steps: allocate a bigger array, copy the old values into it, delete the old array, and point your pointer at the new one. Every resizable list in C++ does exactly this behind the scenes.

Code story: A list starts with room for 2 numbers, but 2 more numbers arrive. The program allocates a new array of 4, copies the old 2 numbers across, fills the 2 new slots, deletes the old array, and repoints the pointer at the new one. Watch the order β€” the old array is deleted only AFTER the copy, never before.

#include <iostream>
using namespace std;

int main() {
    int size = 2;
    int* nums = new int[size]{5, 10};

    int newSize = 4;
    int* bigger = new int[newSize];
    for (int i = 0; i < size; i++) {
        bigger[i] = nums[i]; // copy the old values over
    }
    bigger[2] = 15;
    bigger[3] = 20;

    delete[] nums; // free the old, smaller array
    nums = bigger; // point at the new array
    size = newSize;

    cout << "Old size: 2" << endl;
    cout << "New size: " << size << endl;
    cout << "Numbers:";
    for (int i = 0; i < size; i++) {
        cout << " " << nums[i];
    }
    cout << endl;
    delete[] nums;
    return 0;
}
Old size: 2
New size: 4
Numbers: 5 10 15 20

What just happened? The new array bigger starts empty β€” only the copy loop brings the old values across. The old array is freed with delete[] nums; and then the pointer nums is repointed, so the rest of the program carries on using the same name. This is a manual job that is easy to get wrong β€” which is why real programs usually let std::vector (Lesson 38) do it for them. But now you know exactly what a vector does under the hood.

If you delete the old array BEFORE copying, the values are gone and the copy reads freed memory. Order matters: allocate, copy, then delete.

6. Common Mistakes with Dynamic Allocation

MistakeWhat happensFix
Forgetting delete[] for an arrayThe memory is never freed β€” a leak.Always pair new[] with delete[].
Using delete (no brackets) on an arrayUndefined behaviour β€” only the first element is freed.Write delete[] for every array from new[].
Using the array after delete[]You read or write freed memory β€” the program may crash.Set the pointer to nullptr right after deleting it.
Deleting the same pointer twiceThe second delete frees memory that is already free β€” undefined behaviour, often a crash.Set the pointer to nullptr after deleting it.
Allocating inside a loop without freeingEvery round leaks memory β€” the program slowly eats more and more.Free each allocation before the next round, or allocate once outside the loop.

Practice β€” Dynamic Allocation

Try these on your own. Type each program, run it, and check the output matches what you expect.

  1. Store 5 temperatures in a dynamically allocated array, print the highest one, then delete[] the array.
  2. Allocate a single string on the heap with new, set it to your name, print it, delete it, and set the pointer to nullptr.
  3. Make a small class called Point with x and y, allocate one Point with new, set it to (3, 4), print the point, then delete it.
  4. Start with an array of 2 names, grow it to 4 names using the copy pattern from Section 5, print all 4, then delete[] it.

How do you allocate an array of 10 integers on the heap?


What frees an array made with new[]?


You allocate an array inside a function and return its pointer without deleting it. What happens?


Fill in the blanks β€” type the missing word into the box.

An array made with must be freed with delete[].


Free an array made with new[] by calling .


After deleting a pointer, set it to so you can test it safely later.


Up Next

Namespaces

Next, stop name clashes β€” give your code its own address.