SlideShare a Scribd company logo
How to Adopt Modern C++17 into Your C++ Code
BRK2146
 What’s new?
 “Classic” vs. “Modern” C++
 Move semantics; vocabulary types
 “Top two” general issues/techniques
 RAII + scopes
 Error handling
 “One more”
 Pointers: Dumb and smart (and smart used correctly)
 “What should every C++ programmer be
expected to know?”
 For years, there has not been a single source to
point to.
 Now there is. Readable on a long plane flight.
 Recommend it heavily!
 Also a demonstration that modern C++ is simpler to
teach and explain.
 Currently being updated for C++17,
Second Edition ETA July 2018.
Then: C++98 code
circle* p = new circle( 42 );
vector<shape*> v = load_shapes();
for( vector<shape*>::iterator i=v.begin(); i!=v.end(); ++i ){
if( *i && **i == *p )
cout << **i << “ is a matchn”;
}
// … later, possibly elsewhere …
for( vector<shape*>::iterator i = v.begin();
i != v.end(); ++i ) {
delete *i;
}
delete p;
Then: C++98 code Now: Modern C++
circle* p = new circle( 42 );
vector<shape*> v = load_shapes();
for( vector<shape*>::iterator i=v.begin(); i!=v.end(); ++i ){
if( *i && **i == *p )
cout << **i << “ is a matchn”;
}
// … later, possibly elsewhere …
for( vector<shape*>::iterator i = v.begin();
i != v.end(); ++i ) {
delete *i;
}
delete p;
auto p = make_shared<circle>( 42 );
auto v = load_shapes();
for( auto& s : v ) {
if( s && *s == *p )
cout << *s << “ is a matchn”;
}
T*  shared_ptr<T>
new  make_unique
or make_shared
no need for “delete” –
automatic lifetime management
exception-safe
range-for
auto type deduction
not exception-safe
missing try/catch,
__try/__finally
Python Modern C++
def mean(seq):
n = 0.0
for x in seq:
n += x
return n / len(seq)
auto mean(const Sequence& seq) {
auto n = 0.0;
for (auto x : seq)
n += x;
return n / seq.size();
}
using a concept
(note: not yet VC++)
automatic return
type deduction
Python Modern C++
def mean(seq):
return sum(seq) / len(seq)
mean = lambda seq: sum(seq) / len(seq)
auto mean(const Sequence& seq)
{ return reduce(begin(seq),end(seq)) / seq.size(); }
auto mean = [](const Sequence& seq)
{ return reduce(begin(seq),end(seq)) / seq.size(); }
Python C++17 with parallel STL
def mean(seq):
return sum(seq) / len(seq)
mean = lambda seq: sum(seq) / len(seq)
auto mean(const Sequence& seq) {
return reduce(par_unseq,begin(seq),end(seq))
/ seq.size();
}
auto mean = [](const Sequence& seq) {
return reduce(par_unseq,begin(seq),end(seq))
/ seq.size();
}
 What’s new?
 “Classic” vs. “Modern” C++
 Move semantics; vocabulary types
 “Top two” general issues/techniques
 RAII + scopes
 Error handling
 “One more”
 Pointers: Dumb and smart (and smart used correctly)
Then: C++98 code Now: Modern C++
set<widget>* load_huge_data() {
set<widget>* ret = new set<widget>();
// … load data and populate *ret …
return ret;
}
widgets = load_huge_data();
use(*widgets);
set<widget> load_huge_data() {
set<widget> ret;
// … load data and populate ret …
return ret;
}
widgets = load_huge_data();
use(widgets);
efficient, no deep copy
clean semantics of value types
+ efficiency of reference types
brittle
just asking for returned pointer to
be leaked, dangled, etc.
extra indirection throughout code
 string_view
 Non-owning const view of any contiguous sequence of characters.
 Variations for different character types (e.g., wide).
 optional<T>
 Contains a T value or “empty.”
 variant<Ts…>
 Contains one value of a fixed set of types.
 any
 Contains one value of any type, fully dynamic type.
 Non-owning const view of a contiguous sequence of characters.
 Note: NOT null-terminated.
 All these callers, and all their types… … can be made to work with:
std::wstring s; f(s);
wchar_t* s, size_t len; f({s,len});
winrt::hstring s; f(s);
QString s; f(s.toWStringView());
CStringW s; f((LPCSTR)s); void f(wstring_view s);
CComBSTR s; f({s.m_str, s.Length()});
BSTR s; f({s,SysStringLen(s)});
_bstr_t s; f({s,s.length()});
UNICODE_STRING s; f({s.Buffer, s.Length});
/* … known incomplete sample … */
 Contains a T value or “empty.”
std::optional<std::string> create() {
if (something) return “xyzzy”;
else return {}; // or std::nullopt
}
int main() {
auto result = create();
if (result) {
std::cout << *result << ‘n’; // ok
}
try { cout << *result; } // can throw
catch( const bad_optional_access& e) { /*...*/ }
cout << result.value_or(“empty”) << ‘n’; // if empty, prints “empty”
}
 Contains one value of a fixed set of types.
auto v = variant<int,double>(42); // v now holds an int
cout << get<int>(v); // ok, prints “42”
try {
cout << get<double>(v); // error, throws
}
catch( const bad_variant_access& e) {
cout << e.what();
}
v = 3.14159; // v now holds a double
cout << get<double>(v); // now ok, prints “3.14159”
 Contains one value of any type, fully dynamic type.
auto a = any(42); // a now holds an int
cout << any_cast<int>(a); // ok, prints “42”
try {
cout << any_cast<string>(a);// error, throws
}
catch( const bad_any_cast& e) {
cout << e.what();
}
a = “xyzzy”s; // a now holds a std::string
cout << any_cast<string>(a); // now ok, prints “xyzzy”
 What’s new?
 “Classic” vs. “Modern” C++
 Move semantics; vocabulary types
 “Top two” general issues/techniques
 RAII + scopes
 Error handling
 “One more”
 Pointers: Dumb and smart (and smart used correctly)
 Make sure that objects own resources. Pass every “new” object to the
constructor of an object that owns it (e.g., unique_ptr).
void f() {
auto p = make_unique<widget>(…);
my_class x( OpenFile() );
…
} // automatic destruction and deallocation, automatic exception safety
 What if there isn’t already an RAII type?
 Write one.
 Rarely, consider gsl::finally. It is intended as a last resort.
 Easy to abuse: In reviewed code, typically >50% of uses are abuses.
class widget {
private:
gadget g;
public:
void draw();
};
all types are
destructible
lifetime automatically
tied to enclosing object
no leak, exception safe
automatic propagation,
as if “widget.dispose()
{ g.dispose(); }”
void f() {
widget w;
:::
w.draw();
:::
}
lifetime automatically
tied to enclosing scope
constructs w, including
the w.g gadget member
automatic destruction
and deallocation for w
and w.g
automatic exception
safety, as if “finally {
w.g.dispose();
w.dispose(); }”
 Use exceptions/codes only to report errors, defined as the function can’t
do what it advertised (achieve documented success postconditions).
 Merriam-Webster: “an act that … fails to achieve what should be done”
 Much clearer than “use exceptions for exceptional situations.”
 Error codes vs. exceptions? No fundamental difference. Pick one (1).
 Prefer exceptions wherever possible:
 Error codes are ignored by default. (ouch)
 Error codes have to be manually propagated by intermediate code.
 Error codes don’t work well for errors in constructors and operators.
 Error codes interleave “normal” and “error handling” code.
 Use error codes on boundaries with non-C++ code (including C and DLL APIs).
 Precondition and postcondition violations should assert (or fail fast).
 They are logic bugs in the function caller and function callee body,
respectively, that should be reported at development time.
 Also, throwing an exception loses debug stack context.
 Postconditions should include or imply “!SUCCESS_EXIT ||”.
 It’s only a postcondition violation (bug in function body) if we’re returning
success.
 It’s never a postcondition violation if we’re reporting an error
(throwing an exception or returning a non-success code).
 Corollary: If we’re throwing an exception, we never need to explicitly write
“!SUCCESS_EXIT ||” on our postconditions because our success and failure
paths are already explicitly distinct (return vs. throw).
 What’s new?
 “Classic” vs. “Modern” C++
 Move semantics; vocabulary types
 “Top two” general issues/techniques
 RAII + scopes
 Error handling
 “One more”
 Pointers: Dumb and smart (and smart used correctly)
 C++98:
widget* factory();
void caller() {
widget* w = factory();
gadget* g = new gadget();
use( *w, *g );
delete g;
delete w;
}
red  now “mostly wrong” 
 Don’t use owning *, new, delete.
 Except: Encapsulated inside impl
of low-level data structures.
 Modern C++:
unique_ptr<widget> factory();
void caller() {
auto w = factory();
auto g = make_unique<gadget>();
use( *w, *g );
}
 For “new”, use make_unique by default,
make_shared if it will be shared.
 For “delete”, write nothing.
 C++98 “Classic”:
void f( widget& w ) { // if required
use(w);
}
void g( widget* w ) { // if optional
if(w) use(*w);
}
 Modern C++ “Still Classic”:
void f( widget& w ) { // if required
use(w);
}
void g( widget* w ) { // if optional
if(w) use(*w);
}
auto upw = make_unique<widget>();
…
f( *upw );
auto spw = make_shared<widget>();
…
g( spw.get() );
* and & FTW
 Derived-to-base just works:
// void f(const shared_ptr<Base>&);
f( make_shared<Derived>() ); // ok
 Non-const-to-const just works:
// void f(const shared_ptr<const Node>&);
f( make_shared<Node>() ); // ok
 Bonus geek cred if you know the aliasing ctor:
struct Node { Data data; };
shared_ptr<Data> get_data(const shared_ptr<Node>& pn) {
return { pn, &(pn->data) }; // ok
} Node
Data
 Antipattern #1: Parameters
(Note: Any refcounted pointer type.)
void f( refcnt_ptr<widget>& w ) {
use(*w);
} // ?
void f( refcnt_ptr<widget> w ) {
use(*w);
} // ?!?!
 Antipattern #2: Loops
(Note: Any refcounted pointer type.)
refcnt_ptr<widget> w = …;
for( auto& e: baz ) {
auto w2 = w;
use(w2, *w, *w2, whatever);
} // ?!?!?!?!
Example (HT: Andrei Alexandrescu): In late
2013, Facebook RocksDB changed pass-by-
value shared_ptr to pass-*/&.
 4 QPS (100K to 400K) in one benchmark
Example: C++/WinRT factory cache was slow.
“Obvious” suspect: cache’s mutex lock
Actual culprit: >50% time spent in extra
AddRef/Release on returned object
 The reentrancy pitfall (simplified):
// global (static or heap), or aliased local
… shared_ptr<widget> other_p …
void f( widget& w ) {
g();
use(w);
}
void g() {
other_p = … ;
}
void my_code() {
f( *other_p ); // passing *nonlocal
} // should not pass code review
 “Pin” using unaliased local copy.
// global (static or heap), or aliased local
… shared_ptr<widget> other_p …
void f( widget& w ) {
g();
use(w);
}
void g() {
other_p = … ;
}
void my_code() {
auto pin = other_p; // 1 ++ for whole tree
f( *pin ); // ok, *local
}
 The reentrancy pitfall (simplified):
// global (static or heap), or aliased local
… shared_ptr<widget> other_p …
void f( widget& w ) {
g();
use(w);
}
void g() {
other_p = … ;
}
void my_code() {
f( *other_p ); // passing *nonlocal
other_p->foo(); // (or nonlocal->)
} // should not pass code review
 “Pin” using unaliased local copy.
// global (static or heap), or aliased local
… shared_ptr<widget> other_p …
void f( widget& w ) {
g();
use(w);
}
void g() {
other_p = … ;
}
void my_code() {
auto pin = other_p; // 1 ++ for whole tree
f( *pin ); // ok, *local
pin->foo(); // ok, local->
}
unique_ptr<widget> factory(); // source – produces widget
void sink( unique_ptr<widget> ); // sink – consumes widget
void reseat( unique_ptr<widget>& ); // “will” or “might” reseat ptr
void thinko( const unique_ptr<widget>& ); // usually not what you want
shared_ptr<widget> factory(); // source + shared ownership
// when you know it will be shared, perhaps by factory itself
void share( shared_ptr<widget> ); // share – “will” retain refcount
void reseat( shared_ptr<widget>& ); // “might” reseat ptr
void may_share( const shared_ptr<widget>& ); // “might” retain refcount
1. Never pass smart pointers (by value or by reference) unless you actually
want to manipulate the pointer  store, change, or let go of a reference.
 Prefer passing objects by * or & as usual – just like always.
 Remember: Take unaliased+local copy at the top of a call tree, don’t pass f(*other_p).
 Else if you do want to manipulate lifetime, great, do it as on previous slide.
2. Express ownership using unique_ptr wherever possible, including when
you don’t know whether the object will actually ever be shared.
 It’s free = exactly the cost of a raw pointer, by design.
 It’s safe = better than a raw pointer, including exception-safe.
 It’s declarative = expresses intended uniqueness and source/sink semantics.
 It removes many (often most) objects out of the ref counted population.
3. Else use make_shared up front wherever possible, if object will be shared.
 What’s new?
 “Classic” vs. “Modern” C++
 Move semantics; vocabulary types
 “Top two” general issues/techniques
 RAII + scopes
 Error handling
 “One more”
 Pointers: Dumb and smart (and smart used correctly)
How to Adopt Modern C++17 into Your C++ Code
 … and it turns out we’ve already been doing it.
 Given a set<string> myset, consider:
// C++98
pair<set<string>::iterator,bool> result = myset.insert( “Hello” );
if (result.second) do_something_with( result.first ); // workaround
// C++11 – sweet backward compat
auto result = myset.insert( “Hello” ); // nicer syntax, and the
if (result.second) do_something_with( result.first ); // workaround still works
// C++17
auto [ iter, success ] = myset.insert( “Hello” ); // normal return value
if (success) do_something_with( iter );
BRK2146
How to Adopt Modern C++17 into Your C++ Code

More Related Content

How to Adopt Modern C++17 into Your C++ Code

  • 3.  What’s new?  “Classic” vs. “Modern” C++  Move semantics; vocabulary types  “Top two” general issues/techniques  RAII + scopes  Error handling  “One more”  Pointers: Dumb and smart (and smart used correctly)
  • 4.  “What should every C++ programmer be expected to know?”  For years, there has not been a single source to point to.  Now there is. Readable on a long plane flight.  Recommend it heavily!  Also a demonstration that modern C++ is simpler to teach and explain.  Currently being updated for C++17, Second Edition ETA July 2018.
  • 5. Then: C++98 code circle* p = new circle( 42 ); vector<shape*> v = load_shapes(); for( vector<shape*>::iterator i=v.begin(); i!=v.end(); ++i ){ if( *i && **i == *p ) cout << **i << “ is a matchn”; } // … later, possibly elsewhere … for( vector<shape*>::iterator i = v.begin(); i != v.end(); ++i ) { delete *i; } delete p;
  • 6. Then: C++98 code Now: Modern C++ circle* p = new circle( 42 ); vector<shape*> v = load_shapes(); for( vector<shape*>::iterator i=v.begin(); i!=v.end(); ++i ){ if( *i && **i == *p ) cout << **i << “ is a matchn”; } // … later, possibly elsewhere … for( vector<shape*>::iterator i = v.begin(); i != v.end(); ++i ) { delete *i; } delete p; auto p = make_shared<circle>( 42 ); auto v = load_shapes(); for( auto& s : v ) { if( s && *s == *p ) cout << *s << “ is a matchn”; } T*  shared_ptr<T> new  make_unique or make_shared no need for “delete” – automatic lifetime management exception-safe range-for auto type deduction not exception-safe missing try/catch, __try/__finally
  • 7. Python Modern C++ def mean(seq): n = 0.0 for x in seq: n += x return n / len(seq) auto mean(const Sequence& seq) { auto n = 0.0; for (auto x : seq) n += x; return n / seq.size(); } using a concept (note: not yet VC++) automatic return type deduction
  • 8. Python Modern C++ def mean(seq): return sum(seq) / len(seq) mean = lambda seq: sum(seq) / len(seq) auto mean(const Sequence& seq) { return reduce(begin(seq),end(seq)) / seq.size(); } auto mean = [](const Sequence& seq) { return reduce(begin(seq),end(seq)) / seq.size(); }
  • 9. Python C++17 with parallel STL def mean(seq): return sum(seq) / len(seq) mean = lambda seq: sum(seq) / len(seq) auto mean(const Sequence& seq) { return reduce(par_unseq,begin(seq),end(seq)) / seq.size(); } auto mean = [](const Sequence& seq) { return reduce(par_unseq,begin(seq),end(seq)) / seq.size(); }
  • 10.  What’s new?  “Classic” vs. “Modern” C++  Move semantics; vocabulary types  “Top two” general issues/techniques  RAII + scopes  Error handling  “One more”  Pointers: Dumb and smart (and smart used correctly)
  • 11. Then: C++98 code Now: Modern C++ set<widget>* load_huge_data() { set<widget>* ret = new set<widget>(); // … load data and populate *ret … return ret; } widgets = load_huge_data(); use(*widgets); set<widget> load_huge_data() { set<widget> ret; // … load data and populate ret … return ret; } widgets = load_huge_data(); use(widgets); efficient, no deep copy clean semantics of value types + efficiency of reference types brittle just asking for returned pointer to be leaked, dangled, etc. extra indirection throughout code
  • 12.  string_view  Non-owning const view of any contiguous sequence of characters.  Variations for different character types (e.g., wide).  optional<T>  Contains a T value or “empty.”  variant<Ts…>  Contains one value of a fixed set of types.  any  Contains one value of any type, fully dynamic type.
  • 13.  Non-owning const view of a contiguous sequence of characters.  Note: NOT null-terminated.  All these callers, and all their types… … can be made to work with: std::wstring s; f(s); wchar_t* s, size_t len; f({s,len}); winrt::hstring s; f(s); QString s; f(s.toWStringView()); CStringW s; f((LPCSTR)s); void f(wstring_view s); CComBSTR s; f({s.m_str, s.Length()}); BSTR s; f({s,SysStringLen(s)}); _bstr_t s; f({s,s.length()}); UNICODE_STRING s; f({s.Buffer, s.Length}); /* … known incomplete sample … */
  • 14.  Contains a T value or “empty.” std::optional<std::string> create() { if (something) return “xyzzy”; else return {}; // or std::nullopt } int main() { auto result = create(); if (result) { std::cout << *result << ‘n’; // ok } try { cout << *result; } // can throw catch( const bad_optional_access& e) { /*...*/ } cout << result.value_or(“empty”) << ‘n’; // if empty, prints “empty” }
  • 15.  Contains one value of a fixed set of types. auto v = variant<int,double>(42); // v now holds an int cout << get<int>(v); // ok, prints “42” try { cout << get<double>(v); // error, throws } catch( const bad_variant_access& e) { cout << e.what(); } v = 3.14159; // v now holds a double cout << get<double>(v); // now ok, prints “3.14159”
  • 16.  Contains one value of any type, fully dynamic type. auto a = any(42); // a now holds an int cout << any_cast<int>(a); // ok, prints “42” try { cout << any_cast<string>(a);// error, throws } catch( const bad_any_cast& e) { cout << e.what(); } a = “xyzzy”s; // a now holds a std::string cout << any_cast<string>(a); // now ok, prints “xyzzy”
  • 17.  What’s new?  “Classic” vs. “Modern” C++  Move semantics; vocabulary types  “Top two” general issues/techniques  RAII + scopes  Error handling  “One more”  Pointers: Dumb and smart (and smart used correctly)
  • 18.  Make sure that objects own resources. Pass every “new” object to the constructor of an object that owns it (e.g., unique_ptr). void f() { auto p = make_unique<widget>(…); my_class x( OpenFile() ); … } // automatic destruction and deallocation, automatic exception safety  What if there isn’t already an RAII type?  Write one.  Rarely, consider gsl::finally. It is intended as a last resort.  Easy to abuse: In reviewed code, typically >50% of uses are abuses.
  • 19. class widget { private: gadget g; public: void draw(); }; all types are destructible lifetime automatically tied to enclosing object no leak, exception safe automatic propagation, as if “widget.dispose() { g.dispose(); }” void f() { widget w; ::: w.draw(); ::: } lifetime automatically tied to enclosing scope constructs w, including the w.g gadget member automatic destruction and deallocation for w and w.g automatic exception safety, as if “finally { w.g.dispose(); w.dispose(); }”
  • 20.  Use exceptions/codes only to report errors, defined as the function can’t do what it advertised (achieve documented success postconditions).  Merriam-Webster: “an act that … fails to achieve what should be done”  Much clearer than “use exceptions for exceptional situations.”  Error codes vs. exceptions? No fundamental difference. Pick one (1).  Prefer exceptions wherever possible:  Error codes are ignored by default. (ouch)  Error codes have to be manually propagated by intermediate code.  Error codes don’t work well for errors in constructors and operators.  Error codes interleave “normal” and “error handling” code.  Use error codes on boundaries with non-C++ code (including C and DLL APIs).
  • 21.  Precondition and postcondition violations should assert (or fail fast).  They are logic bugs in the function caller and function callee body, respectively, that should be reported at development time.  Also, throwing an exception loses debug stack context.  Postconditions should include or imply “!SUCCESS_EXIT ||”.  It’s only a postcondition violation (bug in function body) if we’re returning success.  It’s never a postcondition violation if we’re reporting an error (throwing an exception or returning a non-success code).  Corollary: If we’re throwing an exception, we never need to explicitly write “!SUCCESS_EXIT ||” on our postconditions because our success and failure paths are already explicitly distinct (return vs. throw).
  • 22.  What’s new?  “Classic” vs. “Modern” C++  Move semantics; vocabulary types  “Top two” general issues/techniques  RAII + scopes  Error handling  “One more”  Pointers: Dumb and smart (and smart used correctly)
  • 23.  C++98: widget* factory(); void caller() { widget* w = factory(); gadget* g = new gadget(); use( *w, *g ); delete g; delete w; } red  now “mostly wrong”   Don’t use owning *, new, delete.  Except: Encapsulated inside impl of low-level data structures.  Modern C++: unique_ptr<widget> factory(); void caller() { auto w = factory(); auto g = make_unique<gadget>(); use( *w, *g ); }  For “new”, use make_unique by default, make_shared if it will be shared.  For “delete”, write nothing.
  • 24.  C++98 “Classic”: void f( widget& w ) { // if required use(w); } void g( widget* w ) { // if optional if(w) use(*w); }  Modern C++ “Still Classic”: void f( widget& w ) { // if required use(w); } void g( widget* w ) { // if optional if(w) use(*w); } auto upw = make_unique<widget>(); … f( *upw ); auto spw = make_shared<widget>(); … g( spw.get() ); * and & FTW
  • 25.  Derived-to-base just works: // void f(const shared_ptr<Base>&); f( make_shared<Derived>() ); // ok  Non-const-to-const just works: // void f(const shared_ptr<const Node>&); f( make_shared<Node>() ); // ok  Bonus geek cred if you know the aliasing ctor: struct Node { Data data; }; shared_ptr<Data> get_data(const shared_ptr<Node>& pn) { return { pn, &(pn->data) }; // ok } Node Data
  • 26.  Antipattern #1: Parameters (Note: Any refcounted pointer type.) void f( refcnt_ptr<widget>& w ) { use(*w); } // ? void f( refcnt_ptr<widget> w ) { use(*w); } // ?!?!  Antipattern #2: Loops (Note: Any refcounted pointer type.) refcnt_ptr<widget> w = …; for( auto& e: baz ) { auto w2 = w; use(w2, *w, *w2, whatever); } // ?!?!?!?! Example (HT: Andrei Alexandrescu): In late 2013, Facebook RocksDB changed pass-by- value shared_ptr to pass-*/&.  4 QPS (100K to 400K) in one benchmark Example: C++/WinRT factory cache was slow. “Obvious” suspect: cache’s mutex lock Actual culprit: >50% time spent in extra AddRef/Release on returned object
  • 27.  The reentrancy pitfall (simplified): // global (static or heap), or aliased local … shared_ptr<widget> other_p … void f( widget& w ) { g(); use(w); } void g() { other_p = … ; } void my_code() { f( *other_p ); // passing *nonlocal } // should not pass code review  “Pin” using unaliased local copy. // global (static or heap), or aliased local … shared_ptr<widget> other_p … void f( widget& w ) { g(); use(w); } void g() { other_p = … ; } void my_code() { auto pin = other_p; // 1 ++ for whole tree f( *pin ); // ok, *local }
  • 28.  The reentrancy pitfall (simplified): // global (static or heap), or aliased local … shared_ptr<widget> other_p … void f( widget& w ) { g(); use(w); } void g() { other_p = … ; } void my_code() { f( *other_p ); // passing *nonlocal other_p->foo(); // (or nonlocal->) } // should not pass code review  “Pin” using unaliased local copy. // global (static or heap), or aliased local … shared_ptr<widget> other_p … void f( widget& w ) { g(); use(w); } void g() { other_p = … ; } void my_code() { auto pin = other_p; // 1 ++ for whole tree f( *pin ); // ok, *local pin->foo(); // ok, local-> }
  • 29. unique_ptr<widget> factory(); // source – produces widget void sink( unique_ptr<widget> ); // sink – consumes widget void reseat( unique_ptr<widget>& ); // “will” or “might” reseat ptr void thinko( const unique_ptr<widget>& ); // usually not what you want shared_ptr<widget> factory(); // source + shared ownership // when you know it will be shared, perhaps by factory itself void share( shared_ptr<widget> ); // share – “will” retain refcount void reseat( shared_ptr<widget>& ); // “might” reseat ptr void may_share( const shared_ptr<widget>& ); // “might” retain refcount
  • 30. 1. Never pass smart pointers (by value or by reference) unless you actually want to manipulate the pointer  store, change, or let go of a reference.  Prefer passing objects by * or & as usual – just like always.  Remember: Take unaliased+local copy at the top of a call tree, don’t pass f(*other_p).  Else if you do want to manipulate lifetime, great, do it as on previous slide. 2. Express ownership using unique_ptr wherever possible, including when you don’t know whether the object will actually ever be shared.  It’s free = exactly the cost of a raw pointer, by design.  It’s safe = better than a raw pointer, including exception-safe.  It’s declarative = expresses intended uniqueness and source/sink semantics.  It removes many (often most) objects out of the ref counted population. 3. Else use make_shared up front wherever possible, if object will be shared.
  • 31.  What’s new?  “Classic” vs. “Modern” C++  Move semantics; vocabulary types  “Top two” general issues/techniques  RAII + scopes  Error handling  “One more”  Pointers: Dumb and smart (and smart used correctly)
  • 33.  … and it turns out we’ve already been doing it.  Given a set<string> myset, consider: // C++98 pair<set<string>::iterator,bool> result = myset.insert( “Hello” ); if (result.second) do_something_with( result.first ); // workaround // C++11 – sweet backward compat auto result = myset.insert( “Hello” ); // nicer syntax, and the if (result.second) do_something_with( result.first ); // workaround still works // C++17 auto [ iter, success ] = myset.insert( “Hello” ); // normal return value if (success) do_something_with( iter );