CS253: Software Development with C++

Spring 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()
0x6292b0
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()
0x13542b0

What if you’re wrong?

Maybe I’m wrong, and the pointer’s dtor really deletes the memory. So what does this code do?

int main() {
    int *p = new int[10];
    int *q = p;
    cout << q << endl;
}
0x1ce52b0

Two pointers—did the dynamic memory get freed twice? If so, this code is the same:

int main() {
    int *p = new int[10];
    int *q = p;
    cout << q << endl;
    delete[] p;
    delete[] q;
}
0x17652b0
free(): double free detected in tcache 2
SIGABRT: Aborted

It’s not.

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?