new and delete
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.
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}
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:
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}
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:
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}
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[]:
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}
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:
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}
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.
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}
Each pass does a new, showing +; only the i == 1 pass does a delete, showing -. The other two Boxes leak, and their destructors never run. b is a loop variable that goes away at the end of each pass, but the heap block it points to does not.
Using it after delete
The opposite mistake is more dangerous: it's been deleted, the pointer still points there, and you keep using it:
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}
heap-use-after-freeWhat 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:
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}
attempting double-freeWhat 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:
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}
247
alloc-dealloc-mismatchWhat 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:
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}
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.
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.
Ticket issued: seat 12 Watching the movie Ticket void: seat 12
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}
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.
55
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}
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.
Sale price: 48
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}
What you learned
newcreates something on the heap and gives you its address;deletegives it back. With objects, the constructor runs atnewand the destructor atdelete.- Heap things don't vanish when a function ends, so they can be handed on, but someone must
deletethem. new type[n]goes withdelete[],newwithdelete; never mix them.- Forgetting
deleteis 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, andnew[]withdeleteare all undefined behavior (the engine stops). - Modern C++ writes little
new/delete: avectorfor a row of things, next lesson's smart pointers for one object.