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
#include "Loud.h"
int main(int argc, char **) {
auto p = new Loud;
if (argc < 5) {
cerr << "Not enough arguments.\n";
return 1;
}
delete p;
return 0;
}
Loud::Loud()
Not enough arguments.
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 exists, but its use is discouraged.
You’ve 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
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
example #1
#include "Loud.h"
int main(int argc, char **) {
unique_ptr<Loud> p(new Loud);
if (argc < 5) {
cerr << "Not enough arguments.\n";
return 1;
}
// don’t need to delete
return 0;
}
Loud::Loud()
Not enough arguments.
Loud::~Loud()
unique_ptr example #2
#include "Loud.h"
void foo() {
unique_ptr<Loud> p(new Loud);
throw logic_error("boo hoo");
}
int main() {
try {
foo();
}
catch (const exception &e) {
cerr << e.what() << '\n';
}
return 0;
}
Loud::Loud()
Loud::~Loud()
boo hoo
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
example
#include "Loud.h"
int main() {
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
#include "Loud.h"
int main() {
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
#include "Loud.h"
int main() {
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(cpp)»()
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 die of a ketchup-related incident; no effect on the internet service.
weak_ptr
example
#include <iostream>
#include <memory>
using namespace std;
weak_ptr<int> wp;
void observe() {
cout << "use_count=" << wp.use_count() << ": ";
if (auto sp = wp.lock())
cout << *sp << '\n';
else
cout << "wp is expired\n";
}
int main() {
{
shared_ptr<int> mem(new int(42));
wp = mem;
observe();
}
observe();
}
use_count=1: 42
use_count=0: wp is expired
weak_ptr
: why?
- 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.