CS253: Software Development with C++

Fall 2022

Smart Pointers

Show Lecture.SmartPointers as a slide show.

CS253 Smart Pointers

👉👈

Inclusion

To use smart pointers, you need to:

    
#include <memory>

Overview

The Problem

int main() {
    Loud *p = new Loud;
    cout << p << '\n';
    delete p;
}
Loud::Loud()
0x1ab02b0
Loud::~Loud()

Great. The Loud’s constructor got called, and later the Loud’s destructor got called.

The Problem

int main() {
    Loud *p = new Loud;
    cout << p << '\n';
}
Loud::Loud()
0x1cc32b0

Really

Honest—the dtor for a plain pointer does nothing. It does not delete the memory. If it did, what would this code do?

int main() {
    int data[] = {11,22,33,44,55};
    int *p = data+3;
    cout << *p << '\n';
    // p falls out of scope
}
44

When p falls out of scope, its destructor is called. Do you think that this would delete[] the array data, which is on the stack? No, it would not!

Not all pointers point to dynamic data.

The Problem

Loud *p = new Loud;
if (getuid() != 0) {
    cerr << "Wrong user!\n";   // Heckendorn weeps.
    return 1;                  // 🦡
}
delete p;
Loud::Loud()
Wrong user!

Three kinds of smart pointers

  1. unique_ptr if the thing (or array of things) has one owner
  2. shared_ptr if it (or they) has several owners
  3. weak_ptr for voyeurs & lurkers
  4. auto_ptr for people who use obsolete features

😝 auto_ptr 😝

Unique Ownership

🧦

unique_ptr

unique_ptr example #1

unique_ptr<Loud> p(new Loud);
if (getuid() != 0) {
    cerr << "Wrong user!\n";
    return 1;
}
// don’t need to delete
Loud::Loud()
Wrong user!
Loud::~Loud()

unique_ptr example #2

void foo() {
    unique_ptr<Loud> p(new Loud);
    if (getuid() != 0)
        throw logic_error("Wrong user!");
}

int main() {
    try {
        foo();
    }
    catch (const exception &e) {
        cerr << e.what() << '\n';
    }
    return 0;
}
Loud::Loud()
Loud::~Loud()
Wrong user!

unique_ptr array example

unique_ptr<Loud[]> p(new Loud[3]);
cout << "Hello\n";
Loud::Loud()
Loud::Loud()
Loud::Loud()
Hello
Loud::~Loud()
Loud::~Loud()
Loud::~Loud()

Shared Ownership

shared_ptr

shared_ptr example

shared_ptr<Loud> p(new Loud);
cout << p.use_count() << '\n';
{
    cout << p.use_count() << '\n';
    auto q=p;
    cout << p.use_count() << '\n';
    cout << q.use_count() << '\n';
}
cout << p.use_count() << '\n';
Loud::Loud()
1
1
2
2
1
Loud::~Loud()

vector of shared_ptr example

vector<shared_ptr<Loud>> a;
{
    vector<shared_ptr<Loud>> b;
    b.push_back(shared_ptr<Loud>(new Loud('x')));
    b.push_back(shared_ptr<Loud>(new Loud('y')));
    b.push_back(shared_ptr<Loud>(new Loud('z')));
    a = b;
}
cout << "done\n";
Loud::Loud() [c='x']
Loud::Loud() [c='y']
Loud::Loud() [c='z']
done
Loud::~Loud() [c='x']
Loud::~Loud() [c='y']
Loud::~Loud() [c='z']

The a = b assignment only copied (shared) pointers, not the actual Loud objects. We’d have heard, if it did.

vector of shared_ptr example

vector<shared_ptr<Loud>> a;
{
    vector<shared_ptr<Loud>> b;
    b.emplace_back(new Loud('x'));
    b.emplace_back(new Loud('y'));
    b.emplace_back(new Loud('z'));
    a = b;
}
cout << "done\n";
Loud::Loud() [c='x']
Loud::Loud() [c='y']
Loud::Loud() [c='z']
done
Loud::~Loud() [c='x']
Loud::~Loud() [c='y']
Loud::~Loud() [c='z']

vector::emplace_back() builds a shared_ptr in place inside the vector, rather than creating/copying/destroying a temporary shared_ptr.

Did it really do copying in the previous example?

Weak Pointers

Weak Pointers

Weak Pointers

weak_ptr example

weak_ptr<int> wp;

void observe() {
    cout << "use_count=" << wp.use_count() << ": ";
    if (auto sp = wp.lock())
        cout << *sp << '\n';
    else
        cout << "expired\n";
}

int main() {
    {
        shared_ptr<int> mem(new int(42));
        wp = mem;
        observe();
    }

    observe();
}
use_count=1: 42
use_count=0: expired

weak_ptr: why?