c++11: feel the new language
TRANSCRIPT
C++11Feel the new language
Where is C++?
What’s special about C++
• Build abstractions at little or no cost• Without sacrificing transparency• Without sacrificing performance• Pay as you go• Dive under the hood whenever you like
Lightweight abstraction programming languageB.Stroustrup
What makes C++11 different
Direct language support for powerful patterns Lambdas, move semantics
Incorporating best practices and libs into STL– Smart pointers, regular expressions
Catch-up with the modern hardware– Multi-threaded memory model
Shifting programming style to higher level– Naked pointers are persona non grata
E.g.
E.g.
E.g.
E.g.
What is a lightweight abstraction?
goto:– Low-level (machine thinking)– Welcome to spaghetti code
if-then-else, while-loop, loop-until:– Abstracise goto– High-level, meaningful (human thinking)– Composable, self-contained building blocks– Transparent– No performance cost– Can still use goto if necessary
Smart pointers and Resource Handling
Lightweight abstraction: memory
Raw pointers (T*):– Low-level (machine thinking)– Welcome to crashes and memory leaks
Smart pointers (unique_ptr<T>, shared_ptr<T>):– Abstracise raw pointers– High-level, meaningful (human thinking)– Transparent– Little or no performance cost– Can still use raw pointers if necessary
Raw pointers (or HANDLEs)
widget *getWidget();
void foo(){ widget *pw = getWidget();
// Don’t know if we’re sharing the widget with someone else // Don’t know if we must delete pw when done // Have to manually take care about exception-safety
…}
unique_ptr<T>
• Ownership semantics• Think auto_ptr<T> done right• Calls delete or delete[] in destructor• Movable, but not copyable (see Move Semantics later)• No runtime overhead
#include <memory>
std::unique_ptr<widget> getWidget();
void foo(){ std::unique_ptr<widget> pw{ getWidget() }; // The widget is exclusively ours // We can’t accidentally share it // It will be deleted automatically and exception-safe}
shared_ptr<T>• Ref-counting semantics• Last one calls delete in destructor• Transparent and deterministic• Ref-counting and synchronization overhead• Use make_shared to reduce overhead• Use weak_ptr to break cycles
#include <memory>
std::shared_ptr<widget> getWidget();
void foo(){ std::shared_ptr<widget> pw{ getWidget() }; // The widget may be shared // We don’t own it so we don’t care about deleting it}
shared_ptr<T>
shared_ptr<T>
shared_ptr<T>
T
counter control block
Resource Acquisition is Initializationvoid f(){ database *pdb = open_database("mydb"); … // What if we return or throw here? close_database(pdb);}
class DBConnection{private: database *_pdb;
public: explicit DBConnection(const char *name) : pdb(open_database(dbname)) {} ~DBConnection() { close_database(pdb); }}
void f(){ DBConnection dbc("mydb"); … // The db connection is guaranteed to close properly}
• GC in other languages only handles one resource - memory
• RAII in C++ has been around for over a decade• C++11 encourages its use as default• Smart pointers help building RAII around
legacy interfaces• Move semantics makes passing resources
around cheap
Resource Acquisition is Initialization
Tradeoff That Isn’t
What’s wrong with low-level?
• Unsafe? – Yes• Tedious? – Yes• Gets in the way of DRY? – Yes• Best performance? – No. May even make it worse
Low-level programming is a powerful and complex tool, but it doesn’t guarantee you any advantage unless used properly.
Move Semantics
Value semantics as copy semantics
Matrix operator+( const Matrix& a, const Matrix& b ){ Matrix r; // Loop with r[i,j] = a[i,j]+b[i,j] return r; // Copying happens here – expensive}
• Value semantics traditionally means copy-semantics• Which means object gets copied when travels from one place to
another• Which makes returning an object from a function expensive
• Which requires ugly workaroundsvoid operator+( const Matrix& a, const Matrix& b, Matrix& r ){ // Loop with r[i,j] = a[i,j]+b[i,j]}
Move constructor (and =)template <typename T> class Matrix{private: unsigned int m; // Number of rows unsigned int n; // Number of columns T *pelems; // Pointer to an array of m*n elements
public: …
// Copy constructor
Matrix( const Matrix& other ) : m(other.m), n(other.n) { pelems = new T[m*n]; memcpy( pelems, other.pelems, m*n*sizeof(T) ); }
// Move constructor
Matrix( Matrix&& other ) { pelems = other.pelems; other.pelems = nullptr; }
~Matrix(){ delete[] pelems; }};
Move semantics
• Readable• Efficient• Safe• Meaningful• Backward-compatible• Fully supported by STL
Matrix m = a * transpose(b) + c * inv(d);
Rvalue references
• The right function is selected through overload resolution
• Rvalue reference is preferred when called with rvalue• Non-constness of the rvalue reference allows to
modify the rvalue• Bonus: perfect forwarding
Matrix( const Matrix& other ); // const lvalue refMatrix( Matrix&& other ); // non-const rvalue ref
Copy or Move?• Implicit
Matrix f( const Matrix& a ){ Matrix r(a); // copy: a can be used below, don’t mess with it … return r; // move: r is on its last leg}
• ExplicitMatrix f(){ Matrix e{ {1,0}, {0,1} }; Matrix r( std::move(e) ); // forced move: e is zombie now and … // using it would be a bad idea}
Lambdas
λ
Life before lambdas#include <vector>#include <algorithm>#include <iostream>
using namespace std;
struct DivisibilityPredicateFunctor{ int m_d; DivisibilityPredicate( int d ) : m_d(d) {} bool operator()( int n ) const { return n % m_d == 0; }};
void OutputFunction( int n ){ cout << n << endl;};
vector<int> remove_multiples( vector<int> v, int d ){ vector<int> r; remove_copy_if( begin(v), end(v), back_inserter(r), DivisibilityPredicateFunctor(d) ); return r;}
void main(){ vector<int> v;
int array[] = {0,1,2,3,4,5,6,7,8,9}; for( int i = 0; i < sizeof array/sizeof *array; ++i ) v.push_back( array[i] );
vector<int> m = remove_multiples( v, 3 ); for_each( begin(m), end(m), OutputFunction );}
Life in C++11
#include <vector>#include <algorithm>#include <iostream>
using namespace std;
vector<int> remove_multiples( vector<int> v, int d ){ vector<int> r; remove_copy_if( begin(v), end(v), back_inserter(r), [=](int n){ return n % d == 0; } ); return r;}
void main(){ vector<int> m = remove_multiples( {0,1,2,3,4,5,6,7,8,9}, 3 ); for_each( begin(m), end(m), [](int n){ cout << n << endl; } );}
Lambdas
• Without jumping through hoops now:– STL algorithms– Callbacks– Threading
• Foundation for higher-level stuff:– Async programming– Functional programming
C++ flavor: you have fine-grained control over environment capture.
Lambda anatomy
lambda introducer
[=] (int n) -> int { return n % d == 0; }
parameter listcapture list
return type
body
captured variable
Lambda physiology
struct CompilerGeneratedFunctor{ int m_d; CompilerGeneratedFunctor(int d) : m_d(d) {} bool operator()(int n) const { return n % m_d == 0; }};
[d](int n)->bool{ return n % d == 0;}
• Specifying lambda instantiates an object• Invoking lambda calls the object’s operator()• Everything is easily inlinable
• For each lambda compiler generates a class
Lambda physiology
bool CompilerGeneratedFunction(int n){ return n % 2 == 0;}
[](int n)->bool{ return n % 2 == 0;}
• Inlining is even easier
• Lambda with empty capture list is just a function
Variable capture
[=][x, y, z]
By value[&][&x, &y, &z]
By reference[=, &x, &y][&, x, y]
Mix
Recommended watching
http://channel9.msdn.com/Events/GoingNative/GoingNative-2012