Smart pointers
The hard part of last lesson's new and delete is remembering the delete: not too early, not too late, and not never. A smart pointer remembers for you: it's an object holding a pointer, and when it dies (when its destructor runs), it deletes what it points to. Smart pointers are in <memory>.
1#include <iostream>2#include <memory>3#include <string>4 5class Order {6public:7 Order(const std::string& item) : item_(item) {8 std::cout << "Order created: " << item_ << '\n';9 }10 ~Order() {11 std::cout << "Order deleted: " << item_ << '\n';12 }13 void show() const {14 std::cout << "Order: " << item_ << '\n';15 }16 17private:18 std::string item_;19};20 21int main() {22 std::cout << "Start\n";23 std::unique_ptr<Order> order = std::make_unique<Order>("coffee");24 order->show();25 std::cout << "End\n";26 return 0;27}
Start Order created: coffee Order: coffee End Order deleted: coffee
Not a single delete, yet "Order deleted" shows, after "End": when main ends, the variable order dies and deletes the order with it. Press Step to the end: the heap block grays out on the last step, and nothing leaks.
unique_ptr
std::unique_ptr<Order> reads "a smart pointer that alone owns an Order". std::make_unique<Order>("coffee") creates an Order on the heap, passing what's in the parentheses to the constructor, and puts it in a unique_ptr.
You use it like a plain pointer: *p, p->member, and comparing with nullptr. The type name is long, so auto is common:
1#include <iostream>2#include <memory>3 4int main() {5 auto stock = std::make_unique<int>(40);6 *stock -= 15;7 std::cout << "Stock: " << *stock << '\n';8 if (stock != nullptr) {9 std::cout << "Not empty\n";10 }11 return 0;12}
Stock: 25 Not empty
Tip: You can also write std::unique_ptr<int> stock(new int(40));, the only way in C++11. C++14 added std::make_unique, so no new appears in your code; this course always uses it.
When it's deleted
A unique_ptr is an ordinary local variable: wherever it dies, the object is deleted. A pair of curly braces around a few lines shows it best:
1#include <iostream>2#include <memory>3#include <string>4 5class Order {6public:7 Order(const std::string& item) : item_(item) {8 std::cout << "Order created: " << item_ << '\n';9 }10 ~Order() {11 std::cout << "Order deleted: " << item_ << '\n';12 }13 14private:15 std::string item_;16};17 18int main() {19 auto cake = std::make_unique<Order>("cake");20 {21 auto bread = std::make_unique<Order>("bread");22 std::cout << "Inside the braces\n";23 }24 std::cout << "Outside the braces\n";25 cake.reset();26 std::cout << "End\n";27 return 0;28}
Order created: cake Order created: bread Inside the braces Order deleted: bread Outside the braces Order deleted: cake End
Press Step: leaving the } on line 23, bread dies and the bread order goes with it. cake.reset() doesn't wait for the end; it deletes right now (cake = nullptr; does the same). After that, cake is empty.
What does this program show?
1#include <iostream>2#include <memory>3 4class Box {5public:6 Box(char name) : name_(name) {7 std::cout << "+" << name_;8 }9 ~Box() {10 std::cout << "-" << name_;11 }12 13private:14 char name_;15};16 17int main() {18 for (int i = 0; i < 2; i++) {19 auto b = std::make_unique<Box>('a' + i);20 }21 std::cout << '\n';22 return 0;23}
b is a variable in the loop body, so it dies at the end of each pass and the Box is deleted with it: create one, delete it, create another, delete it. Last lesson's loop with new created them and never deleted.
No copies
"Unique" means one: an object belongs to just one unique_ptr. If copies were allowed, both unique_ptrs would delete it when they died, which is last lesson's "delete twice". So copying simply isn't allowed:
1#include <iostream>2#include <memory>3 4int main() {5 auto points = std::make_unique<int>(30);6 std::unique_ptr<int> copy = points;7 std::cout << *copy << '\n';8 return 0;9}
main.cpp: In function 'int main()':
main.cpp:6:33: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
6 | std::unique_ptr<int> copy = points;
| ^~~~~~The error is long; look at the first error:: use of deleted function. That function is the copy constructor of unique_ptr, which is written = delete (lesson 18).
Handing over with std::move
No copies, but you can hand it over: std::move (lesson 18) passes the object from one unique_ptr to another. Afterwards, the original is empty:
1#include <iostream>2#include <memory>3#include <string>4 5class Order {6public:7 Order(const std::string& item) : item_(item) {8 std::cout << "Order created: " << item_ << '\n';9 }10 ~Order() {11 std::cout << "Order deleted: " << item_ << '\n';12 }13 void show() const {14 std::cout << "Order: " << item_ << '\n';15 }16 17private:18 std::string item_;19};20 21void finish(std::unique_ptr<Order> order) {22 order->show();23 std::cout << "Ready\n";24}25 26int main() {27 auto order = std::make_unique<Order>("tea");28 auto counter = std::move(order);29 if (order == nullptr) {30 std::cout << "order is empty now\n";31 }32 finish(std::move(counter));33 std::cout << "Back in main\n";34 return 0;35}
Order created: tea order is empty now Order: tea Ready Order deleted: tea Back in main
Press Step and follow the arrow: at line 28 it moves from order to counter, and order is empty; at line 32 it's handed to finish's parameter. When finish ends, the parameter dies and the order is deleted, so "Order deleted" comes before "Back in main".
A function whose parameter is a std::unique_ptr<Order> (by value) says "give this object to me; it's mine now". The call must say std::move, so you can see at a glance that it's been handed over.
Handing one out
Last lesson's make_order returned a plain pointer, and whoever got it had to remember delete. Return a unique_ptr and nobody does. You can also put them in a vector: when the vector dies, its unique_ptrs die one by one, deleting the objects one by one:
1#include <iostream>2#include <memory>3#include <string>4#include <vector>5 6class Order {7public:8 Order(const std::string& item) : item_(item) {9 std::cout << "Order created: " << item_ << '\n';10 }11 ~Order() {12 std::cout << "Order deleted: " << item_ << '\n';13 }14 15private:16 std::string item_;17};18 19std::unique_ptr<Order> make_order(const std::string& item) {20 auto order = std::make_unique<Order>(item);21 return order;22}23 24int main() {25 std::vector<std::unique_ptr<Order>> orders;26 orders.push_back(make_order("coffee"));27 orders.push_back(make_order("cake"));28 std::cout << orders.size() << " orders in total\n";29 return 0;30}
Order created: coffee Order created: cake 2 orders in total Order deleted: coffee Order deleted: cake
return order; needs no std::move: order is a local variable that dies when the function ends anyway, so the compiler moves it out by itself. Same with push_back(make_order(...)): what goes in is a temporary value. To put in a unique_ptr that has a name, you must write orders.push_back(std::move(p));.
shared_ptr: shared owners
Sometimes an object really has several owners, and there's no telling which finishes last. Then use std::shared_ptr: it can be copied, and each copy counts as an owner. It keeps count of the owners (use_count()), and deletes the object only when the last owner dies.
1#include <iostream>2#include <memory>3#include <string>4 5class Song {6public:7 Song(const std::string& title) : title_(title) {8 std::cout << "Loaded: " << title_ << '\n';9 }10 ~Song() {11 std::cout << "Unloaded: " << title_ << '\n';12 }13 14private:15 std::string title_;16};17 18int main() {19 auto song = std::make_shared<Song>("Sunny Day");20 std::cout << song.use_count() << '\n';21 {22 auto in_playlist = song;23 auto in_favorites = song;24 std::cout << song.use_count() << '\n';25 }26 std::cout << song.use_count() << '\n';27 song.reset();28 std::cout << "End\n";29 return 0;30}
Loaded: Sunny Day 1 3 1 Unloaded: Sunny Day End
Press Step: lines 22 and 23 each make a copy, three arrows point to one block, and the count goes from 1 to 3. Leaving the braces, the two copies die and it's back to 1. song.reset() lets go of the last owner, and only then does "Unloaded" show.
Which to use
| Situation | Use |
|---|---|
| one object on the heap, one owner | std::unique_ptr + std::make_unique (think of this first) |
| really several owners | std::shared_ptr + std::make_shared |
| a row of things | std::vector |
| just borrowing it, not owning | a reference, or a plain pointer |
The last row means: a function that only wants to use an object, not take it over, takes a const Order& (called with *order), not a smart pointer. If you really need a plain pointer, order.get() gives you the address inside, but never delete it; that's the smart pointer's job.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it. Afterwards, press Step to the end and check for blocks left on the heap.
Let make_unique handle it
✓The program creates a ticket with new but forgets to delete it. Without writing delete, switch to std::make_unique so the ticket voids itself. The program should show the three lines below.
Ticket issued: seat 7 Watching the show Ticket void: seat 7
1#include <iostream>2#include <memory>3 4class Ticket {5public:6 Ticket(int seat) : seat_(seat) {7 std::cout << "Ticket issued: seat " << seat_ << '\n';8 }9 ~Ticket() {10 std::cout << "Ticket void: seat " << seat_ << '\n';11 }12 13private:14 int seat_;15};16 17int main() {18 Ticket* t = new Ticket(7);19 std::cout << "Watching the show\n";20 return 0;21}
Fix it: a parcel that can't be copied
✓ship is meant to take over the parcel, but this code doesn't compile. Change one thing so the program shows the three lines below.
On the way Delivered to Lin Next one
1#include <iostream>2#include <memory>3#include <string>4 5class Parcel {6public:7 Parcel(const std::string& to) : to_(to) {}8 ~Parcel() {9 std::cout << "Delivered to " << to_ << '\n';10 }11 12private:13 std::string to_;14};15 16void ship(std::unique_ptr<Parcel> parcel) {17 std::cout << "On the way\n";18}19 20int main() {21 auto parcel = std::make_unique<Parcel>("Lin");22 ship(parcel);23 std::cout << "Next one\n";24 return 0;25}
Count the owners
✓doc is a shared document. Make two more shared_ptr copies of it (alice and bob), then show doc.use_count(). The program should show 3.
3
1#include <iostream>2#include <memory>3#include <string>4 5int main() {6 auto doc = std::make_shared<std::string>("Weekly report");7 std::cout << doc.use_count() << '\n';8 return 0;9}
What you learned
- Smart pointers live in
<memory>; when one dies, itdeletes the object it points to, so you don't writedelete. auto p = std::make_unique<Order>(...); use it like a plain pointer:*p,p->show(), comparing withnullptr.- Wherever a
unique_ptrdies, the object is deleted;reset()or= nullptrdeletes it early. - A
unique_ptrcan't be copied (use of deleted function); hand it over withstd::move, which leaves the old one empty. - Functions can return a
unique_ptr, and avectorcan hold them; astd::unique_ptr<T>parameter means "give it to me". - A
shared_ptrcan be copied;use_count()is the number of owners, and the object is deleted when the last one dies. - Think
unique_ptrfirst; useshared_ptronly for really shared owners; to just borrow, pass a reference.