0% found this document useful (0 votes)
41 views47 pages

Being Smart About Pointers - Michael VanLoon - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
41 views47 pages

Being Smart About Pointers - Michael VanLoon - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 47

Being

 Smart  About  Pointers  

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> pointer reference_container<T> pointer

reference_container<T>

shared_ptr<T2> p3 strong ref count

weak ref count


T2 pointer
T pointer
reference_container<T> pointer
deleter func pointer
shared_ptr  construcNon/assignment  
and  make_shared  
class C {
public:
C() { cout << "C!" << endl; }
~C() { cout << "~C" << endl; }
};

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

strong ref count

weak ref count

T pointer

deleter func pointer


shared_ptr<T2> p3

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); }

static shared_ptr<BAD> create()


{ return make_shared<BAD>(); }
};

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(); }

static shared_ptr<Good> create()


{ return make_shared<Good>(); }
};

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

reference_container<T> pointer reference_container<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

deleter func pointer


shared_ptr  custom  deleter  
shared_res* allocShared() {
// Do some magic to allocate shared resource
return malloc(100);
}

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

deleter func pointer


How  it  works:  shared_ptr  
• High  level  overview:  
• Contains  a  pointer  and  a  reference  count  that  
is  shared  among  all  instances  
• Contains  opNonal  custom  deleter  
• Last  releaser  decrements  reference  to  zero,  
and  object  is  deleted  
Interface  SemanNcs  
or  How  to  Pass  Your  Stuff  Around  
• FuncNons  that  receive  resources  controlled  by  
shared_ptr  can  be  wrifen  with  several  different  
interfaces:  
– foo(shared_ptr<Widget>)
– foo(const shared_ptr<Widget>&)
– foo(shared_ptr<Widget>&&) [C++11]
– foo(Widget&)
– foo(const Widget&)
– foo(Widget*)
– foo(const Widget*)
• The  interface  you  choose  says  a  lot  about  what  you  
intend  for  both  the  caller  and  the  callee  
Interface  SemanNcs  
or  How  to  Pass  Your  Stuff  Around  
foo(shared_ptr<Widget>)
• Caller  contract:  
– Caller  is  required  to  wrap  object  in  shared_ptr,  which  
implies  that  Widget  object  cannot  be  stack-­‐based  
• Callee  contract:  
– Callee  is  allowed  to  take  an  ownership  stake  in  object  
(i.e.  copy  shared_ptr)  
• Other  implicaNons:  
– shared_ptr  is  copied  during  call,  resulNng  in  increment  
of  atomic  variable  
Interface  SemanNcs  
or  How  to  Pass  Your  Stuff  Around  
foo(const shared_ptr<Widget>&)
• Caller  contract:  
– Caller  is  required  to  wrap  object  in  shared_ptr,  which  
implies  that  Widget  object  cannot  be  stack-­‐based  
• Callee  contract:  
– Callee  is  may  take  an  ownership  stake  in  object  (i.e.  
copy  shared_ptr),  but  is  not  required  to  do  so  
• Other  implicaNons:  
– shared_ptr  reference  is  not  copied  during  funcNon  
call,  resulNng  in  befer  performance  
Interface  SemanNcs  
or  How  to  Pass  Your  Stuff  Around  
foo(shared_ptr<Widget>&&) [C++11]
• Caller  contract:  
– Caller  is  required  to  wrap  object  in  shared_ptr,  which  
implies  that  Widget  object  cannot  be  stack-­‐based  
– Caller  will  receive  object  that  has  been  empNed  out  by  
move  operaNon  
• Callee  contract:  
– Callee  must  take  ownership  stake  of  object  (i.e.  move  
shared_ptr)  
• Other  implicaNons:  
– shared_ptr  reference  is  moved  during  funcNon  call  from  
caller’s  copy  to  callee’s  copy  
Interface  SemanNcs  
or  How  to  Pass  Your  Stuff  Around  
foo(Widget&) and  foo(const Widget&)
• Caller  contract:  
– Caller  is  not  required  to  wrap  object  in  shared_ptr,  which  
allows  object  to  be  allocated  either  on  stack  or  heap  
– Caller  retains  full  ownership  of  object  
– Object  is  always  valid  (reference)  
• Callee  contract:  
– Callee  cannot  take  ownership  stake  of  object,  because  
shared_ptr  container  is  absent  
• Other  implicaNons:  
– No  churn  of  atomic  reference  count  
Interface  SemanNcs  
or  How  to  Pass  Your  Stuff  Around  
foo(Widget*)  and  foo(const Widget*)
• Caller  contract:  
– Caller  is  not  required  to  wrap  object  in  shared_ptr,  which  allows  
object  to  be  allocated  either  on  stack  or  heap  
– Caller  retains  full  ownership  of  object  
– Object  may  be  opNonal  (i.e.  NULL  pointer)  
• Callee  contract:  
– Callee  cannot  take  ownership  stake  of  object,  because  
shared_ptr  container  is  absent  
• Other  implicaNons:  
– No  churn  of  atomic  reference  count  
– Prefer  reference  over  pointer  unless  object  must  be  opNonal  
Interface  SemanNcs  
Key  Points  
• Choose  to  pass  by  Widget&  or  Widget*,  
unless  the  callee  is  expected  to  take  an  
ownership  stake  in  the  object  
– Passing  by  Widget  type  does  not  impose  container  
or  allocaNon  constraints  on  caller  
– Choose  to  pass  by  Widget&  over  Widget*,  unless  
the  Widget  object  is  opNonal  (may  be  NULL)  
• Choose  to  pass  by  const shared_ptr<Widget>&
if  the  callee  may  take  ownership  in  object  
– shared_ptr  reference  may  be  copied  if  needed,  but  
will  not  make  an  unnecessary  copy  on  funcNon  entry  
Summary  
• Use  a  smart  pointer.    It  avoids  the  explicit,  
problemaNc  manual  delete  
• Prefer  unique_ptr  (or  boost::scoped_ptr  if  you  
don’t  have  C++11  yet)  because  of  its  efficiency  
and  simplicity  if  possible  
• Use  shared_ptr  for  any  variables  that  need  
shared  semanNcs  
• Use  make_shared  when  possible;  it  may  be  more  
efficient,  and  avoids  explicit  new  
• Consider  object-­‐based  reference  counNng  for  
cases  where  it  fits  a  paradigm  befer  

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy