Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 3 of 7

new asks for heap memory and returns a pointer to it. delete gives it back.

#include <iostream>

int main() {
    int* p = new int(42);        // one int on the heap, initialised to 42

    std::cout << *p << "\n";
    *p = 7;
    std::cout << *p << "\n";

    delete p;                    // released
    return 0;
}
42
7

For arrays there is a second pair, and they are not interchangeable:

#include <iostream>

int main() {
    int count = 4;
    int* values = new int[count];      // uninitialised

    for (int i = 0; i < count; ++i) {
        values[i] = (i + 1) * 10;
    }
    for (int i = 0; i < count; ++i) {
        std::cout << values[i] << " ";
    }
    std::cout << "\n";

    delete[] values;                   // note the brackets
    return 0;
}
10 20 30 40 

new[] pairs with delete[]. new pairs with delete. Mixing them is undefined behaviour, and one of the mismatches usually appears to work, which makes it worse than if it always crashed.

Note new int[count] leaves the elements uninitialised. new int[count]() zeroes them.

The five ways this goes wrong

Every one of these is a real bug people ship. Read them as the case against the syntax you just learned.

1. You forget delete. The memory is never returned — a leak (lesson 8.4).

2. You delete twice. Undefined behaviour, usually a crash or heap corruption.

3. You use the pointer after deleting. Use-after-free (lesson 8.5).

4. You return early and skip the delete:

void process(bool bail) {
    int* data = new int[100];

    if (bail) {
        return;              // leaked: delete[] never runs
    }

    delete[] data;
}

Every return, break, and continue between the new and the delete is a leak waiting to be added by whoever edits this next.

5. Something throws in between. If an exception is thrown after the new and before the delete, the delete is skipped and the memory leaks. This one cannot be fixed by being careful, because the throw can come from any function you call.

Points 4 and 5 are the important ones: they are not carelessness, they are the structure of manual memory management being wrong.

The same code, correct by construction

#include <iostream>
#include <vector>

void process(bool bail) {
    std::vector<int> data(100, 7);      // owns its heap memory

    if (bail) {
        std::cout << "bailed, memory freed anyway\n";
        return;                          // destructor runs
    }

    std::cout << "finished, " << data.size() << " elements\n";
}

int main() {
    process(true);
    process(false);
    return 0;
}
bailed, memory freed anyway
finished, 100 elements

No new. No delete. No leak on any path, including one that throws. The destructor is not something you remember to call — it is something the language calls for you.

What to write instead

| Instead of | Write | |---|---| | new int[n] / delete[] | std::vector<int> | | new char[n] for text | std::string | | new T for one owned object | std::unique_ptr<T> (Module 10) | | new T shared between owners | std::shared_ptr<T> (Module 10) | | A pointer that just looks at something | a raw T* or T&, owning nothing |

That last row matters: raw pointers are not banned. What is discouraged is raw pointers that own memory. A raw pointer used as a non-owning view is normal, modern C++.

So why learn new at all?

Three reasons, all practical:

  • You will read it. Millions of lines of existing C++ use it, and you cannot maintain code you cannot read.
  • It is what the containers do inside. std::vector calls the allocator so you do not have to; knowing what it is doing makes its behaviour predictable rather than magical.
  • You will occasionally write it — inside a class that owns a resource, when no existing wrapper fits. Module 9's Rule of Three exists for exactly that case.

The guidance is not "never" but "not in ordinary code." If you are writing new in a function body, there is nearly always a container or smart pointer that does it better.

Try it

Write a function that allocates an array with new[], fills it, prints it, and frees it. Then add an early return in the middle and identify the leak. Finally rewrite it with std::vector and confirm the early return is now harmless.

Checkpoint

  • new allocates on the heap; delete frees it. new[] pairs with delete[], never mixed.
  • new int[n] leaves elements uninitialised; new int[n]() zeroes them.
  • Early returns and thrown exceptions skip the delete — a structural flaw, not carelessness.
  • std::vector, std::string and smart pointers free their memory on every exit path.
  • Raw pointers are fine as non-owning views; what is discouraged is raw pointers that own.
  • Learn new to read existing code and to write resource-owning classes, not for everyday work.

Questions about this lesson

Log in to ask a question.