Kodewick
Lesson 21 of 30Contents

Smart pointers

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

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>.

main.cpp
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}
Output
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:

main.cpp
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}
Output
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:

main.cpp
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}
Output
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.

Guess first

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}

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:

main.cpp
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}
Output
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:

main.cpp
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}
Output
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:

main.cpp
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}
Output
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.

main.cpp
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}
Output
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

SituationUse
one object on the heap, one ownerstd::unique_ptr + std::make_unique (think of this first)
really several ownersstd::shared_ptr + std::make_shared
a row of thingsstd::vector
just borrowing it, not owninga 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.

Exercise 1

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.

It should print
Ticket issued: seat 7
Watching the show
Ticket void: seat 7
exercise1.cpp
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}
Output
Press Run or Ctrl+Enter
Exercise 2

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.

It should print
On the way
Delivered to Lin
Next one
exercise2.cpp
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}
Output
Press Run or Ctrl+Enter
Exercise 3

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.

It should print
3
exercise3.cpp
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}
Output
Press Run or Ctrl+Enter

What you learned

  • Smart pointers live in <memory>; when one dies, it deletes the object it points to, so you don't write delete.
  • auto p = std::make_unique<Order>(...); use it like a plain pointer: *p, p->show(), comparing with nullptr.
  • Wherever a unique_ptr dies, the object is deleted; reset() or = nullptr deletes it early.
  • A unique_ptr can't be copied (use of deleted function); hand it over with std::move, which leaves the old one empty.
  • Functions can return a unique_ptr, and a vector can hold them; a std::unique_ptr<T> parameter means "give it to me".
  • A shared_ptr can be copied; use_count() is the number of owners, and the object is deleted when the last one dies.
  • Think unique_ptr first; use shared_ptr only for really shared owners; to just borrow, pass a reference.