Kodewick
Lesson 20 of 30Contents

new and delete

Lesson 20 of 30 · Part 5 · Memory and lambdas · about 15 minutes

So far, every variable has been declared inside a function, and disappears when the function ends. Such variables live on the stack, and the compiler handles their life and death. C++ has another place called the heap: you ask it for memory with new, and give it back yourself with delete when you're done.

main.cpp
1#include <iostream>2 3int main() {4    int* stock = new int(40);5    std::cout << *stock << '\n';6    *stock -= 15;7    std::cout << *stock << '\n';8    delete stock;9    return 0;10}
Output
40
25

new int(40) creates an int on the heap with the value 40 and gives you its address. This int has no name; the only way to reach it is through the pointer stock. delete stock; gives it back. Press Step: after line 4, a block appears on the heap with an arrow from stock; after line 8, the block grays out, given back.

new with objects

new can create objects too. The constructor runs at new, and the destructor runs at delete:

main.cpp
1#include <iostream>2#include <string>3 4class Order {5public:6    Order(const std::string& item) : item_(item) {7        std::cout << "Order created: " << item_ << '\n';8    }9    ~Order() {10        std::cout << "Order deleted: " << item_ << '\n';11    }12    void show() const {13        std::cout << "Order: " << item_ << '\n';14    }15 16private:17    std::string item_;18};19 20int main() {21    std::cout << "Start\n";22    Order* order = new Order{"coffee"};23    order->show();24    delete order;25    std::cout << "End\n";26    return 0;27}
Output
Start
Order created: coffee
Order: coffee
Order deleted: coffee
End

Press Step and watch the lines and the heap: at line 22 the block appears and "Order created" shows; at line 24 "Order deleted" shows and the block grays out. The pointer holds the object's address, so member functions are called with -> (last lesson).

Outliving a function

A variable on the stack is gone when its function ends. Things on the heap are different: they stay until someone deletes them. So a function can build something on the heap and hand out its address:

main.cpp
1#include <iostream>2#include <string>3 4class Order {5public:6    Order(const std::string& item) : item_(item) {7        std::cout << "Order created: " << item_ << '\n';8    }9    ~Order() {10        std::cout << "Order deleted: " << item_ << '\n';11    }12    void show() const {13        std::cout << "Order: " << item_ << '\n';14    }15 16private:17    std::string item_;18};19 20Order* make_order(const std::string& item) {21    Order* order = new Order{item};22    return order;23}24 25int main() {26    Order* tea = make_order("tea");27    tea->show();28    delete tea;29    return 0;30}
Output
Order created: tea
Order: tea
Order deleted: tea

Press Step: the make_order layer ends and disappears, but the order on the heap is still there, and tea in main now points to it. The price: whoever gets the address must remember to delete it.

new[] and delete[]

new type[count] asks for a whole row at once, and the count can be a variable. p[i] gets element i, just like a vector. To give it back, write delete[]:

main.cpp
1#include <iostream>2 3int main() {4    int days = 5;5    double* temps = new double[days];6    for (int i = 0; i < days; i++) {7        temps[i] = 20 + i * 1.5;8    }9    double total = 0;10    for (int i = 0; i < days; i++) {11        total += temps[i];12    }13    std::cout << "Average temperature: " << total / days << '\n';14    delete[] temps;15    return 0;16}
Output
Average temperature: 23

Press Step and watch the row of boxes on the heap fill one by one. new goes with delete, new[] with delete[]; don't mix them.

Note: The boxes from new double[days] hold no definite values; fill them yourself, and don't read them before that. If you want values from the start, a vector is easier.

Forgetting delete: memory leaks

Taking memory and never giving it back is a memory leak. There is no error, and the output doesn't show it:

main.cpp
1#include <iostream>2#include <string>3 4class Order {5public:6    Order(const std::string& item) : item_(item) {7        std::cout << "Order created: " << item_ << '\n';8    }9    ~Order() {10        std::cout << "Order deleted: " << item_ << '\n';11    }12 13private:14    std::string item_;15};16 17int main() {18    Order* order = new Order{"cake"};19    order = new Order{"bread"};20    delete order;21    return 0;22}
Output
Order created: cake
Order created: bread
Order deleted: bread

Only one "Order deleted". Press Step: after line 19, the arrow from order swings from "cake" to "bread", and no arrow points to the "cake" block anymore, so nobody can use it or give it back. At the end it's still on the heap, marked as leaked.

For a program that runs and ends, the system takes all its memory back at the end, so no harm shows. But a program that stays running (a server, a game) and leaks a little each time uses more and more memory.

Guess first

What does this program show?

1#include <iostream>2 3class Box {4public:5    Box() {6        std::cout << "+";7    }8    ~Box() {9        std::cout << "-";10    }11};12 13int main() {14    for (int i = 0; i < 3; i++) {15        Box* b = new Box{};16        if (i == 1) {17            delete b;18        }19    }20    std::cout << '\n';21    return 0;22}

Using it after delete

The opposite mistake is more dangerous: it's been deleted, the pointer still points there, and you keep using it:

main.cpp
1#include <iostream>2 3int main() {4    int* balance = new int(500);5    delete balance;6    *balance += 100;7    std::cout << *balance << '\n';8    return 0;9}
Output
heap-use-after-free

What happened: Stopped at line 6. This memory is no longer yours: it was deleted (or a vector or string grew and moved), or it belonged to a function that has returned.

After delete, the address in balance is unchanged, but that memory isn't yours anymore and may be handed to something else at any time. A real program may show 600 anyway, show something else, or go wrong somewhere else much later: undefined behavior. Our engine stops at line 6; press "see how it went wrong".

Deleting the same block twice is wrong too. It happens most easily when two pointers point to one block:

main.cpp
1#include <iostream>2 3int main() {4    int* points = new int(30);5    int* copy = points;6    *copy += 5;7    delete points;8    delete copy;9    return 0;10}
Output
attempting double-free

What happened: Stopped at line 8. This memory was deleted twice.

copy = points copies only the address, so both arrows point to the same block. Line 7 already gave it back, line 8 gives it back again, and the engine stops.

Tip: Right after delete p;, write p = nullptr;. If you use it again by mistake, at least it's an obvious null pointer; and delete nullptr does nothing, which is not an error.

new[] with plain delete

Giving back a row from new[] with just delete is undefined behavior as well:

main.cpp
1#include <iostream>2 3int main() {4    int* scores = new int[3];5    scores[0] = 90;6    scores[1] = 75;7    scores[2] = 82;8    std::cout << scores[0] + scores[1] + scores[2] << '\n';9    delete scores;10    return 0;11}
Output
247
alloc-dealloc-mismatch

What happened: Stopped at line 9. Memory from new[] is given back with delete[], and memory from new with delete: the two don't mix.

With -Wall, g++ warns (mismatched-new-delete); our engine stops at line 9. Changing it to delete[] scores; fixes it.

Why to write less new

Every mistake above depends on people remembering: each new has exactly one matching delete, at the right time, of the right kind. As programs grow and functions return halfway, something gets missed. So modern C++ rarely writes new and delete directly:

  • For a row of things, use std::vector: it gets its boxes on the heap and gives them back itself.
  • For one object on the heap, use next lesson's smart pointers: when nobody uses it anymore, it deletes itself.

Here's the average temperature example with a vector: not one delete, and no leak:

main.cpp
1#include <iostream>2#include <vector>3 4int main() {5    int days = 5;6    std::vector<double> temps(days);7    for (int i = 0; i < days; i++) {8        temps[i] = 20 + i * 1.5;9    }10    double total = 0;11    for (double t : temps) {12        total += t;13    }14    std::cout << "Average temperature: " << total / days << '\n';15    return 0;16}
Output
Average temperature: 23

std::vector<double> temps(days) uses parentheses: it makes days boxes, all 0. (With curly braces, {days} would mean "one element, the value 5".)

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it. After each one, press Step to the end and see what's left on the heap.

Exercise 1

Give it back

The program news a Ticket but never gives it back. Add one line so its destructor runs and the program shows the three lines below.

It should print
Ticket issued: seat 12
Watching the movie
Ticket void: seat 12
exercise1.cpp
1#include <iostream>2 3class Ticket {4public:5    Ticket(int seat) : seat_(seat) {6        std::cout << "Ticket issued: seat " << seat_ << '\n';7    }8    ~Ticket() {9        std::cout << "Ticket void: seat " << seat_ << '\n';10    }11 12private:13    int seat_;14};15 16int main() {17    Ticket* t = new Ticket{12};18    std::cout << "Watching the movie\n";19    return 0;20}
Output
Press Run or Ctrl+Enter
Exercise 2

Squares on the heap

Use new int[n] to get n boxes, store (i + 1) * (i + 1) in box i, add them up and show the total, then give them back with delete[]. n is 5, and the program should show 55.

It should print
55
exercise2.cpp
1#include <iostream>2 3int main() {4    int n = 5;5    int* squares = new int[n];6    // fill each box7    int total = 0;8    // add the boxes into total9    std::cout << total << '\n';10    // give them back11    return 0;12}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: given back too soon

The program keeps using price after delete. Reorder the lines (don't remove the delete) so the program shows Sale price: 48.

It should print
Sale price: 48
exercise3.cpp
1#include <iostream>2 3int main() {4    int* price = new int(60);5    *price = *price * 4 / 5;6    delete price;7    std::cout << "Sale price: " << *price << '\n';8    return 0;9}
Output
Press Run or Ctrl+Enter

What you learned

  • new creates something on the heap and gives you its address; delete gives it back. With objects, the constructor runs at new and the destructor at delete.
  • Heap things don't vanish when a function ends, so they can be handed on, but someone must delete them.
  • new type[n] goes with delete[], new with delete; never mix them.
  • Forgetting delete is a memory leak: no error and nothing in the output; press Step to see the blocks left on the heap.
  • Using after delete, deleting twice, and new[] with delete are all undefined behavior (the engine stops).
  • Modern C++ writes little new / delete: a vector for a row of things, next lesson's smart pointers for one object.