Being Smart About Pointers - Michael VanLoon - CppCon 2015
Being Smart About Pointers - Michael VanLoon - CppCon 2015
Michael
VanLoon
Adapted
from
CPPcon
2014
m.vanloon@F5.com
A
Quick
Diversion
• Let’s
talk
about
RAII…
RAII:
Resource
AcquisiNon
is
IniNalizaNon
• Scope-‐Based
Resource
Management
• An
idiom
for
managing
resources
based
on
container
object
lifeNme
• Most
oRen
used
in
scoped
variable
scenarios
• AutomaNc
and
low
maintenance
• Reduces
the
opportunity
for
negligence
• How?
DeterminisNc,
EncapsulaNon,
Locality,
ExcepNon
Safety
RAII
• DeterminisNc:
– DeallocaNon
of
resources
happens
immediately,
during
container
object
destrucNon
– Standard
scoping
and
object
lifeNme
rules
apply
– Standard
ordering
rules
apply
– No
delayed
garbage
collecNon
RAII
• EncapsulaNon:
– Cleanup
code
is
kept
with
other
logically-‐related
object
management
code
– Cleanup
code
is
not
sprinkled
throughout
your
program
wherever
an
object
might
exit
scope
RAII
• Locality:
– Constructor,
Destructor,
other
object
management
code,
and
container
methods
are
all
logically
and
physically
grouped
together
RAII
• ExcepNon
Safety:
– Stack-‐based
variables
will
exit
scope
as
stack
is
unwound
following
excepNon
– SBRM-‐based
variables
will
automaNcally
clean
up
resources
as
they
exit
scope
(lack
of)
RAII
example
old
and
busted
>>
new
hotness
class C int
{ main(int argc, const char* argv[])
public: {
C(int i): x(i) { } for (int i = 0; i < 100; ++i)
~C() { } {
private: C* c = new C(i);
int x; /* do something with c */
}; delete c;
}
return 0;
}
RAII
example
old
and
busted
>>
new
hotness
class C; int
main(int argc, const char* argv[])
class SBRM {
{ for (int i = 0; i < 100; ++i)
public:
SBRM(C* c): pc(c) {
{ } SBRM cc(new C(i));
~SBRM() C* c = cc;
{ delete pc; }
/* do something with c */
operator C*()
{ return pc; } }
private: return 0;
C* pc; }
};
Smart
pointers,
the
toolbox
• Boost::scoped_ptr
and
std::unique_ptr
(C++11)
• shared_ptr,
with
make_shared
and
enable_shared_from_this
• weak_ptr
Smart
pointers,
what
they
can
do
• Features
– RAII,
with
all
its
benefits
– Custom
deleters
–
not
just
new/delete!
– Reference
counted
in
some
cases
– Guarantee
of
determinisNc
delete
• Benefits
– No
more
memory
leaks
or
mulNple
frees
– Code
easier
to
read,
easier
to
maintain,
with
less
bugs
– Simpler
code
logic;
no
messy
ownership
tracking
Pointer
Best
PracNces
• Avoid
explicit
new/delete
whenever
possible
– e.g.
make_shared,
make_unique,
or
custom
factory
• Avoid
delete
except
for
excepNonal
circumstances
– It’s
a
sign
you
need
to
re-‐think
pointer
handling
code
– It’s
easy
to
forget
– It’s
easy
to
accidentally
maintain
around
• Avoid
handling
raw
pointers
whenever
reasonable
– Put
them
into
a
container
as
soon
as
you
get
them
– Even
befer:
use
make_shared
or
make_unique
• How?
– Boost::scoped_ptr
or
std::unique_ptr
– shared_ptr
– Custom
container
of
your
own
design
boost::scoped_ptr
and
std::unique_ptr
• Both
adhere
to
RAII
principles
• Both
appropriate
for
managing
local,
scope-‐based
resources
• Both
acNvely
prevent
unintenNonal
sharing
of
resource
• Both
are
faster
and
simpler
than
shared_ptr
• Prefer
unique_ptr
if
you
have
it.
If
not,
scoped_ptr
and
shared_ptr
solve
many
of
the
same
problems.
boost::scoped_ptr
• Available
in
boost
• scoped_ptr
has
neither
Move
nor
Copy
semanNcs;
it
is
truly
local
scope
only
• Separate
scoped_array
for
new
[]
/
delete
[]
• Does
not
support
custom
deleter
std::unique_ptr
• C++11
improvement
on
scoped_ptr
• unique_ptr
has
Move
semanNcs
but
not
Copy
semanNcs
(i.e.
can
return
out
of
funcNon
and
be
embedded
in
standard
containers)
• unique_ptr
naNvely
supports
delete
[]
• unique_ptr
allows
custom
deleter
• In
C++14,
has
make_unique
which
is
more
excepNon
safe
and
supports
avoidance
of
explicit
new
and
pointer
handling
boost::scoped_ptr
example
class C {
public:
C() { cout << "C!" << endl; }
~C() { cout << "~C" << endl; }
};
C* Cfactory() {
return new C;
}
void spFunc() {
boost::scoped_ptr<C> c(Cfactory());
// Do something with c...
}
std::unique_ptr
example
unique_ptr<C> returnUP() {
unique_ptr<C> c(new C);
// Do some magic with c, but we're not final owner
return c;
}
void upFunc() {
unique_ptr<C> c(returnUP());
// Do something with c; we are the final owner
}
void upVecFunc() {
vector<unique_ptr<C>> cVec;
for (int i = 0; i < 5; ++i)
cVec.emplace_back(new C);
// cVec goes out of scope and cleans up nicely
}
shared_ptr,
make_shared
and
enable_shared_from_this
• shared_ptr
adheres
to
RAII
principles
• Reference-‐counted
container
• Copyable
• Custom
deleter
supported
for
cleaning
up
resource
allocaNons
other
than
new
• make_shared
allocates
object
more
efficiently,
is
excepNon
safe,
and
avoids
explicit
new
• enable_shared_from_this
allows
object
to
return
shared_ptr
containing
itself
• Prefer
unique/scoped_ptr
over
shared_ptr
Example
shared_ptr
implementaNon
shared_ptr<T> p1 shared_ptr<T> p2
T pointer T pointer
reference_container<T>
void spAssign() {
shared_ptr<C> p1 = make_shared<C>();
assert(p1.use_count() == 1);
shared_ptr<C> p2 = p1;
assert(p2.use_count() == 2);
shared_ptr<C> p3(new C);
assert(p3.use_count() == 1);
p2 = p3;
assert(p1.use_count() == 1 && p3.use_count() == 2);
}
Example
make_shared
opNmizaNon
shared_ptr<T> p1 shared_ptr<T> p2
T pointer
reference_container<T>
reference_container<T> pointer
T pointer
T object T2 pointer
reference_container<T> pointer
state var 1
state var 2
shared_ptr
copy/move
and
embedding
in
containers
shared_ptr<C> Cfactory() {
return make_shared<C>();
}
void spVec() {
vector< shared_ptr<C> > cVec;
cVec.emplace_back(Cfactory()); // first C
cVec.emplace_back(Cfactory()); // second C
// Do something with cVec...
// everything cleans up nicely
}
shared_ptr
share-‐aware
class
and
enable_shared_from_this
class BAD {
public:
shared_ptr<BAD> getSP()
{ return shared_ptr<BAD>(this); }
shared_ptr<BAD> beBAD() {
shared_ptr<BAD> p1 = BAD::create();
BAD* raw = p1.get();
shared_ptr<BAD> p2(raw->getSP());
return p1;
}
shared_ptr
share-‐aware
class
and
enable_shared_from_this
class Good: public enable_shared_from_this<Good> {
public:
shared_ptr<Good> getSP()
{ return shared_from_this(); }
shared_ptr<Good> doGood() {
shared_ptr<Good> p1 = Good::create();
Good* raw = p1.get();
shared_ptr<Good> p2(raw->getSP());
return p1;
}
Example
enable_shared_from_this
shared_ptr<MyObject> p1 shared_ptr<MyObject> p2
T pointer T pointer
MyObject reference_container
<MyObject>
MyObject state…
strong ref count
enable_shared_from_this<MyObject>
weak ref count
reference_container<T> pointer
T pointer
void releaseShared(shared_res* p) {
// Do some magic to release shared resource
free(p);
}
void testSharedResource() {
shared_ptr<shared_res> sr(allocShared(), releaseShared);
// Do something with sr...
// safe and automatic cleanup
}
weak_ptr
• Helps
break
circular
references
• Holds
a
non-‐owning
“weak”
reference
• Contained
resource
can
only
be
used
by
requesNng
a
shared_ptr
from
the
weak_ptr
• A
weak_ptr
will
not
prevent
resource
from
being
destroyed
(but
does
delay
destrucNon
of
shared
ref
count
block)
weak_ptr
void wpTwiddler() {
boost::weak_ptr<C> w1;
{
boost::shared_ptr<C> p1 = boost::make_shared<C>();
assert(p1.use_count() == 1);
w1 = p1;
assert(p1.use_count() == 1); // weak adds no ref
assert(!w1.expired()); // w1 is usable?
boost::shared_ptr<C> p2 = w1.lock();
assert(p2); // not empty
assert(p2.use_count() == 2);
}
assert(w1.expired() && w1.use_count() == 0);
}
What
is
a
smart
pointer?
• It’s
a
container
for
a
pointer
– It
holds
your
pointer
and
gives
it
back
to
you
– It
manages
the
lifeNme
of
the
held
resource
– It
facilitates
RAII
• Constructs
with
reasonable
defaults
• Cleans
up
pointer
at
appropriate
Nme
• (Ideally)
properly
conveys
interface
semanNcs
– Shared,
unique,
etc.
What
is
a
smart
pointer?
• It’s
a
proxy
for
a
pointer
– It
stands
in
for
the
pointer
in
most
cases
where
you
might
use
the
raw
pointer
– In
many
cases
it
can
transparently
act
as
if
it
is
the
original
pointer
type
Various
kinds
of
smart
pointers
• “Generic”
smart
pointers:
– auto_ptr
– scoped_ptr
– unique_ptr
– shared_ptr
– weak_ptr
– _com_ptr_t
– CComPtr
Various
kinds
of
smart
pointers
• Domain-‐specific
smart
pointers:
– std::string
– _bstr_t
– _variant_t
The
different
kinds
of
reference
counNng
• Object-‐based
reference
counNng
– The
count
is
stored
in
the
object
– MicrosoR
COM
model
– SomeNmes
referred
to
as
“intrusive”
(boost::intrusive_ptr)
– Example:
MicrosoR
COM
• Container-‐based
reference
counNng
– The
count
is
stored
in
the
smart
pointer
container
– Example:
shared_ptr
Object-‐based
reference
counNng
• Advantages
– Very
simple
smart
pointer
logic
– Can
easily
mix
with
raw
pointer
usage
– Object
has
greater
visibility
into
its
lifeNme
and
cleanup
– Lighter
weight
overall
• Disadvantages
– Outside
the
COM
world,
less
familiar
– Outside
the
COM
world,
no
ready-‐made
soluNon
– Object
itself
becomes
slightly
bigger
and
more
complex
Container-‐based
reference
counNng
• Advantages
– Can
contain
pointer
to
any
objet;
no
added
object
knowledge
or
complexity
– Very
standard
and
widely
used
• Disadvantages
– Difficult
to
mix
with
raw
pointer
usage
– Heavier
weight
overall
because
of
complexity
of
good
shared_ptr
implementaNon
How
it
works:
object-‐based
reference
counNng
• Sorry
this
talk
is
too
short,
so
here’s
what
you
get:
– SNck
an
atomic
count
in
an
object
– Provide
methods
to
add
and
release
references
– Object
deletes
itself
when
reference
goes
to
zero
How
it
works:
auto_ptr
• Contains
raw
pointer
and
an
ownership
flag
• Ownership
is
transferred
to
last
auto_ptr
that
takes
ownership
of
pointer
• ProblemaNc
and
error-‐prone
ownership
semanNcs,
so
it’s
deprecated!
auto_ptr<T> p
T pointer
bool owner
How
it
works:
scoped_ptr
and
unique_ptr
• Contains
raw
pointer
• Does
now
allow
transfer
of
ownership
(except
via
move
in
unique_ptr),
which
simplifies
logic
• unique_ptr
opNonally
contains
pointer
to
custom
deleter
func,
which
will
be
called
instead
of
delete
if
desired
unique_ptr<T> p
T pointer