Show Lecture.SmartPointers as a slide show.
CS253 Smart Pointers
👉 | | 👈 |
Inclusion
To use smart pointers, you need to:
#include <memory>
Overview
- Smart pointers help you to manage memory.
- You don’t have to remember to delete the memory.
- Remember, no garbage collection!
- Even if you remember to delete the memory,
an exception might prevent that from happening.
The Problem
int main() {
Loud *p = new Loud;
cout << p << '\n';
delete p;
}
Loud::Loud()
0x178e2b0
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()
0x8602b0
- It is clear that the programmer had faith that, when p falls out of
scope, at the end of main, its destructor will free the
dynamically-allocated
Loud
.
- Faith can be a wonderful thing, but it can also be misguided.
Pointers don’t work that way.
- The memory was not freed. If it had been freed,
Loud
’s dtor
would have been printed “Loud::~Loud()
”.
- A pointer’s dtor does nothing, same as the dtor for an int.
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;
}
0xb752b0
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;
}
0xa1f2b0
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!
- Should have put it that
Loud
on the stack.
- The memory never got freed!
- I could duplicate
delete p
, but that’s not very
DRY.
- What if some deep function throws an exception?
Three kinds of smart pointers
- unique_ptr if the thing (or array of things) has one owner
- shared_ptr if it (or they) has several owners
- weak_ptr for voyeurs & lurkers
auto_ptr
for people who use obsolete features
😝 auto_ptr
😝
- An old solution is
auto_ptr
.
- It’s faulty.
auto_ptr
was deprecated in C++2011
- “Deprecated” means that it still existed, but its use was
discouraged. Consider it notice that it will go away someday.
auto_ptr
was removed from C++2017.
- Sure, it’s still on the internet. What isn’t?
- It might even still be there in your compiler.
- Don’t use it.
Unique Ownership
- My socks are uniquely owned by me.
- These socks never been owned by anybody but me.
- I don’t share my socks with anybody.
- When I die, my wife will burn my socks.
- There is no sharing at all going on with my socks.
- unique_ptr is built-in RAII.
- A unique_ptr “owns” the pointed-to object.
- A unique_ptr behaves as some people erroneously think that raw
pointers do: when the unique_ptr is destroyed (usually, by falling
out of scope) then the pointed-to objected is destroyed, as well.
- You cannot copy a unique_ptr. Which one would be the unique
owner?
- No space overhead, and practically no time overhead.
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()
- After executing
return 1;
, p
fell out of scope, and the
unique_ptr destructor was called. That dtor called delete on the
Loud
.
- Smart pointer ctors are marked explicit, so
unique_ptr<Loud> p(new Loud);
works, but
unique_ptr<Loud> p = new Loud;
doesn’t.
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<Loud[]> p(new Loud[3]);
cout << "Hello\n";
Loud::Loud()
Loud::Loud()
Loud::Loud()
Hello
Loud::~Loud()
Loud::~Loud()
Loud::~Loud()
- This is one unique_ptr that points to
an array of
Loud
objects.
- They’re all allocated together, and all destroyed together,
when
p
falls out of scope and so its dtor is called.
- Note the
Loud[]
argument to unique_ptr, which means
“I point to an array, not just a scalar”
Shared Ownership
- My wife & I jointly own our house.
- If my wife were to cease to exist, I would still own the house.
- Or, if I were to cease to exist, my wife would still own the house.
- We might someday sell our house to somebody else.
- Houses are very different than socks.
shared_ptr
- shared_ptr is built-in RAII.
- A shared_ptr is a “counting pointer”. It keeps track of how many
shared owners this object has, via a counter.
- A new owner (via assignment) increments the counter.
- When any owner is destroyed, the counter decrements.
- When the counter goes to zero, there are no more owners,
so the object itself is destroyed.
- You can assign a shared_ptr. It just increments the use count.
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<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<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
- With a unique_ptr, there’s only a single owner.
When the owner is destroyed, the dynamic object is destroyed.
- A single person has a contract with Comcast, an ISP.
- The person moves away, the contract is terminated.
Weak Pointers
- With a shared_ptr, there are multiple owners.
If any of the owners are destroyed, the shared object still exists,
as long as any owners remain.
- A family shares their internet service.
- Junior goes off to college; parents keep using the internet.
Weak Pointers
- What if we want to play the multiple-owner game, but not contribute
any ownership?
- I steal my neighbor’s Wi-Fi.
- I quit my job and move away; no effect on the internet service.
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
- Why would you want a weak_ptr?
- Cached data: if you held a shared_ptr to cached data,
then it would never get freed, because you own it.
This way, you can use it if it’s still around.
- Avoiding circular dependencies, e.g., a doubly-linked list.