Show Lecture.SmartPointers as a slide show.
CS253 Smart Pointers
👉 | ![](https://commons.wikimedia.org/w?title=Special:Redirect/file/Albert_Einstein_Head.jpg&width=300) | 👈 |
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()
0x12942b0
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()
0x19f82b0
- 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.
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!
- 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.