3. What’s special about C++
Lightweight abstraction programming language
B.Stroustrup
• Build abstractions at little or no cost
• Without sacrificing transparency
• Without sacrificing performance
• Pay as you go
• Dive under the hood whenever you like
4. What makes C++11 different
Direct language support for powerful patterns
E.g. Lambdas, move semantics
Incorporating best practices and libs into STL
– Smart pointers, regular expressions
E.g.
Catch-up with the modern hardware
– Multi-threaded memory model
E.g.
Shifting programming style to higher level
– Naked pointers are persona non grata
E.g.
5. 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
7. 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
8. 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
…
}
9. 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
}
10. shared_ptr<T>
• Ref-counting semantics shared_ptr<T> T
• Last one calls delete in destructor
• Transparent and deterministic shared_ptr<T>
• Ref-counting and synchronization overhead
• Use make_shared to reduce overhead shared_ptr<T>
counter
control block
• 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
}
11. Resource Acquisition is Initialization
void 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
}
12. Resource Acquisition is Initialization
• 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
17. 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.
19. Value semantics as copy semantics
• 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
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
}
• Which requires ugly workarounds
void operator+( const Matrix& a, const Matrix& b, Matrix& r )
{
// Loop with r[i,j] = a[i,j]+b[i,j]
}
20.
21.
22.
23. 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; }
};
24. Move semantics
Matrix m = a * transpose(b) + c * inv(d);
• Readable
• Efficient
• Safe
• Meaningful
• Backward-compatible
• Fully supported by STL
25. Rvalue references
Matrix( const Matrix& other ); // const lvalue ref
Matrix( Matrix&& other ); // non-const rvalue ref
• 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
26. 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
}
• Explicit
Matrix 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
}
28. 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 );
}
29. 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; } );
}
30. 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.
31. Lambda anatomy
return type
lambda introducer captured variable
[=] (int n) -> int { return n % d == 0; }
parameter list
capture list body
32. Lambda physiology
• For each lambda compiler generates a class
struct CompilerGeneratedFunctor
{
[d](int n)->bool int m_d;
{ CompilerGeneratedFunctor(int d) : m_d(d) {}
bool operator()(int n) const
return n % d == 0; {
} return n % m_d == 0;
}
};
• Specifying lambda instantiates an object
• Invoking lambda calls the object’s operator()
• Everything is easily inlinable
33. Lambda physiology
• Lambda with empty capture list is just a function
[](int n)->bool bool CompilerGeneratedFunction(int n)
{ {
return n % 2 == 0; return n % 2 == 0;
}
}
• Inlining is even easier