Show Lecture.SmartPointers as a slide show.
CS253 Smart Pointers
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
auto 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 fix it by duplicating
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 has one owner
- shared_ptr if the thing 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.
You’d received fair notice that it will go away someday.
auto_ptr
was removed from C++2017.
- Sure, it’s still on the internet. What isn’t?
- Don’t use it.
- unique_ptr is built-in RAII.
- A unique_ptr “owns” the pointed-to object.
- 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()
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!
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.