yaser zhian dead mage igdi, workshop 10, may 30 th -31 st, 2013

Post on 28-Mar-2015

218 Views

Category:

Documents

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Modern C++A (Hopefully) Practical Introduction

Yaser ZhianDead MageIGDI, Workshop 10, May 30th-31st, 2013

http://yaserzt.com/ 2

Agenda

Today: auto, decltype, range-based for, etc. Lambdas Rvalue references and moving Variadic templates

Tomorrow Threads, atomics and the memory model Other features: initializer lists, constexpr, etc. Library updates: new containers, smart pointers, etc. General Q&A

http://yaserzt.com/ 3

Themes and Takeaways

Ways to write code that is: Cleaner and less error-prone Faster Richer and can do more (occasionally)

Know thy language You can never have too many tools

Elegance in interface; complexity (if any) in implementation

Take everything here with a grain of salt!

http://yaserzt.com/ 4

Examples and Exercises (1/2)

We will use Visual Studio 2012 (with the Nov 2012 CTP compiler update.) Go ahead. Open it up, make a project, add a file,

set the “toolset” in project options. Write a simple “hello, world” and run it.

Please do try and write code; the sound of keyboard does not disrupt the workshop.

http://yaserzt.com/ 5

Examples and Exercises (2/2)

We will also use “IDE One” online compiler at http://ideone.com/ You might want to register an account there. Do so while I talk about unimportant stuff and

answer any questions… Remember to select the C++11 compiler. Write and run a simple program here as well.

http://yaserzt.com/ 6

auto, decltypeand range-based for

http://yaserzt.com/ 7

decltype (1/3)

What is the type of a + b? a? int? double ? Dependent on operator +, and on a and b.▪ And a whole lot of name lookup, type deduction and

overload resolution rules. Even if you don’t know, the compiler always

does.decltype(a + b) c;c = a + b; (Instead of e.g. double c;)

http://yaserzt.com/ 8

decltype (2/3)

What’s the return type of this function?template <typename T, typename U>??? Add (T const & a, U const & b) {

return a + b;}

One answer is decltype(T() + U())▪ Not entirely correct. (Why?)

The correct answer is decltype(a + b) But that won’t compile.

http://yaserzt.com/ 9

decltype (3/3)

What is wrong with this?template <typename T, typename U>decltype(a + b)

Add (T const & a, U const & b) {

return a + b;}

This is basically the motivation behind the new function declaration syntax in C++11.

http://yaserzt.com/ 10

New Function Syntax

auto Fun (type1 p1) -> returntype; The previous function template then becomes:

template <typename T, typename U>auto Add (T const & a, U const & b) -> decltype(a + b){ return a + b;}

This works for ordinary functions too: auto Sqr (float x)->float {return x*x;}

http://yaserzt.com/ 11

auto (1/2)

Putting auto where a type name is expected, instructs the compiler to infer type from initializing expression, e.g. auto foo = a * b + c * d; auto bar = new std::map<std::string, bool>;

auto baz = new std::map< std::pair<std::string, int>, std::vector<bool, my_bool_alloc>>::const_iterator;

http://yaserzt.com/ 12

auto (2/2)

Some more examples: auto x = 0; auto y = do_stuff (x); auto const & y = do_stuff (x); auto f = std::bind (foo, _1, 42); for (auto i = c.begin(), e = c.end(); i != e; ++i) {…}

http://yaserzt.com/ 13

decltype and auto – a caveat

Sometimes, you have to be very careful with auto and decltype:

std::vector<int> const & v (1);auto a = v[0]; // intdecltype(v[1]) b = 1; // int const &auto c = 0; // intauto d = c; // intdecltype(c) e = 1; // intdecltype((c)) f = c; // int &decltype(0) g; // int

http://yaserzt.com/ 14

Range-based for Loop (1/2)

How common is this code snippet?vector<string> v;for (vector<string>::iterator i = v.begin(); i != u.end(); i++) cout << *i << endl; How many problems can you see? Here’s a better version:for (auto i = v.cbegin(), e = v.cend(); i != e; ++i) cout << *i << endl; This is the best version:for (auto const & s : v) cout << s << endl;

http://yaserzt.com/ 15

Range-based for Loop (2/2)

This loop:for (for-range-declaration : expression) statement will get expanded to something like this:{ auto && __range = range-init; for (auto __begin= begin-expr, __end= end-expr; __begin != __end; ++__begin) { for-range-declaration = *__begin; statement }}

http://yaserzt.com/ 16

LambdasIntroducing more “functionality” into C++

http://yaserzt.com/ 17

Lambdas (1/5)

Lambdas are unnamed functions that you can write almost anywhere in your code (that you can write an expression.)

For example: [] (int x) -> int {return x * x;} [] (int x,int y){return x<y ? y : x;}

What does this do? [] (double v) {cout << v;} (4.2);

http://yaserzt.com/ 18

Lambdas (2/5)

Storing lambdas: auto sqr = [] (int x) -> int {return x * x;};auto a = sqr(42);

std::function<int(int, int)> g =[] (int a, int b) {return a + b;};int d = g(43, -1);

auto h = std::bind ( [](int x,int y){return x<y ? y : x;} , _1, 0);auto n = h (-7);

http://yaserzt.com/ 19

Lambdas (3/5)

Consider these functions: template <typename C, typename F>void Apply (C & c, F const & f) { for (auto & v : c) f(v);}

template <typename C, typename T>void Apply2 (C & c, function<void(T&)> const & f) { for (auto & v : c) f(v);}

Used like this: int a [] = {10, 3, 17, -1};Apply (a, [] (int & x) {x += 2;});

http://yaserzt.com/ 20

Lambdas (4/5)

Apply (a, [](int x) {cout << x << endl;});

int y = 2;Apply (a, [y](int & x) {x += y;});

int s = 0;Apply (a, [&s](int x) {s += x;});

Apply (a, [y, &s](int x) {s += x + y;});

http://yaserzt.com/ 21

Lambdas (5/5)

int y = 2;auto f = [y](int & x) {x += y;};y = 10;Apply (a, f);

int y = 2;auto f = [&y](int & x) {x += y;};y = 10;Apply (a, f);

By the way, you can capture everything by value ([=]) or by reference ([&]).

http://yaserzt.com/ 22

Rvalue References

http://yaserzt.com/ 23

Rvalue References and Moving (1/B)

C++ used to have a tendency to copy stuff around if you weren’t paying attention!

What happens when we call this function? vector<string> GenerateNames (){

returnvector<string>(50, string(100,

'*'));}

A whole lot of useless stuff are created and copied around. All sorts of techniques and tricks to avoid those copies.

http://yaserzt.com/ 24

Rvalue References and Moving (2/B)

string s = string("Hello") + " " + "world.";

1. string (char const *)

2. string operator + (string const &, char const *)

3. string operator + (string const &, char const *)

4. this ultimately called the copy c’tor string (string const &). In total, there can be as many as 5 (or even 7) temporary strings

here. (Unrelated note) Some allocations can be avoided with

“Expression Templates”.

http://yaserzt.com/ 25

(Copy Elision and RVO)

When dealing with anonymous temporary objects, the compiler can “elide” their (copy-) construction, which is called “copy elision”.

This is a unique kind of optimization, as the compiler is allowed to remove code that has side effects!

Return Value Optimization is one kind of copy elision.

http://yaserzt.com/ 26

Rvalue References and Moving (3/B)

C++11 introduces “rvalue references” to let you work with (kinda) temporary objects. Rvalue references are denoted with &&. e.g. int && p = 3; or void foo (std::string && s); or Matrix::Matrix (Matrix && that){…}

http://yaserzt.com/ 27

Rvalue References and Moving (4/B)

In situations where you used to copy the data from an object into another object, if your first object is an rvalue (i.e. temporary) now you can “move” the data from that to this.

Two important usages of rvalue references are “move construction” and “move assignment”. e.g. string (string && that);// move c'tor and string & operator = (string && that); // move assignment

http://yaserzt.com/ 28

Rvalue References and Moving (5/B)

template <typename T>class Matrix{private:

T * m_data;unsigned m_rows, m_columns;

public:Matrix (unsigned rows, unsigned columns);~Matrix ();Matrix (Matrix<T> const & that);template <typename U> Matrix (Matrix<U> const &

that);Matrix<T> & operator = (Matrix<T> const & that);Matrix (Matrix<T> && that);Matrix<T> & operator = (Matrix<T> && that);...

};

http://yaserzt.com/ 29

Rvalue References and Moving (6/B)

template <typename T>class Matrix{

...unsigned rows () const;unsigned columns () const;unsigned size () const;T & operator () (unsigned row, unsigned col);// m(5, 7) =

0;T const & operator () (unsigned row, unsigned col) const;

template <typename U>auto operator + (Matrix<U> const & rhs) const

-> Matrix<decltype(T() + U())>;

template <typename U>auto operator * (Matrix<U> const & rhs) const

-> Matrix<decltype(T() * U() + T() * U())>;};

http://yaserzt.com/ 30

Rvalue References and Moving (7/B)

Matrix (unsigned rows, unsigned columns) : m_rows (rows), m_columns (columns) , m_data (new T [rows * columns]){}

~Matrix (){ delete[] m_data;}

Matrix (Matrix<T> const & that) : m_rows (that.m_rows), m_columns (that.m_columns) , m_data (new T [that.m_rows * that.m_columns]){ std::copy ( that.m_data, that.m_data + (m_rows * m_columns), m_data );}

http://yaserzt.com/ 31

Rvalue References and Moving (8/B)

Matrix<T> & operator = (Matrix<T> const & that){ if (this != &that) { T * new_data = new T [that.m_rows * that.m_columns]; std::copy ( that.m_data, that.m_data + (m_rows * m_columns), new_data ); delete[] m_data; m_data = new_data; m_rows = that.m_rows; m_columns = that.m_columns; } return *this;}

http://yaserzt.com/ 32

Rvalue References and Moving (9/B)

Matrix (Matrix<T> && that) : m_rows (that.m_rows), m_columns (that.m_columns) , m_data (that.m_data){ that.m_rows = that.m_columns = 0; that.m_data = nullptr;}

http://yaserzt.com/ 33

Rvalue References and Moving (A/B)

Matrix<T> & operator = (Matrix<T> && that){ if (this != &that) { delete[] m_data; m_rows = that.m_rows; m_columns = that.m_columns; m_data = that.data; that.m_rows = rhs.m_columns = 0; that.m_data = nullptr; } return *this;}

http://yaserzt.com/ 34

Rvalue References and Moving (B/B)

struct SomeClass{ string s; vector<int> v;public: // WRONG! WRONG! WRONG! // Doesn’t move, just copies. SomeClass (SomeClass && that) : s (that.s), v (that.v) {}

SomeClass (SomeClass && that) : s (std::move(that.s)), v (std::move(that.v)) {}};

http://yaserzt.com/ 35

std::move (1/8)

In principle, std::move should look like this:template <typename T>??? move (??? something){ return something;}

What should the argument type be? T&& ? T& ? Both? Neither?

We need to be able to pass in both lvalues and rvalues.

http://yaserzt.com/ 36

std::move (2/8)

We can overload move() like this: move (T && something) move (T & something) But that will lead to exponential explosion of overloads if the function

has more arguments. “Reference collapse” rule in C++98:

int& & is collapsed to int&. In C++11, the rules are: (in addition to the above)

int&& & is collapsed to int&. int& && is collapsed to int&. int&& && is collapsed to int&&.

http://yaserzt.com/ 37

std::move (3/8)

Therefore, only the T&& version should be enough. If you pass in an lvalue to our move, the actual argument

type will collapse into T&, which is what we want (probably.) So, move looks like this thus far:template <typename T>??? move (T && something){ return something;}

http://yaserzt.com/ 38

std::move (4/8)

Now, what is the return type? T&& ? It should be T&& in the end. But if we declare it so, and move() is called on an lvalue,▪ then T will be SomeType&▪ then T&& will be SomeType& &&▪ then it will collapse into SomeType&▪ then we will be returning an lvalue reference from move(),

which will prevent any moving at all. We need a way to remove the & if T already has one.

http://yaserzt.com/ 39

std::move (5/8)

We need a mechanism to map one type to another In this case, to map T& and T&& to T, and T to T.

There is no simple way to describe the process, but this is how it’s done: template<typename T> struct RemoveReference{ typedef T type;};

With that, RemoveReference<int>::type will be equivalent to int.

But we are not done.

http://yaserzt.com/ 40

std::move (6/8)

Now we specialize:template<typename T>struct RemoveReference<T&>{ typedef T type;};

template<typename T>struct RemoveReference<T &&>{ typedef T type;};

Now, RemoveReference<int &>::type will be int too.

http://yaserzt.com/ 41

std::move (7/8)

Our move now has the correct signature:template <typename T>typename RemoveReference<T>::type &&move (T && something){ return something;}

But it’s not correct. That “something” in there is an lvalue, remember?

http://yaserzt.com/ 42

std::move (8/8)

…so we cast it to an rvalue reference:template <typename T>typename RemoveReference<T>::type && move (T && something){ return static_cast< typename RemoveReference<T>::type && > (something);}

Hopefully, this is correct now!

http://yaserzt.com/ 43

Universal References

There is no such thing as universal references! But, due to the C++11 reference collapsing,

sometimes when you write T && v, you can get anything; both lvalues and rvalues.

These can be thought of as “universal references”.

Two preconditions: There must be T&&, And there must be type deduction.

http://yaserzt.com/ 44

End of Day 1Any questions?

http://yaserzt.com/ 45

ScopeGuardA Simple Method to Do RAII and Transactions

http://yaserzt.com/ 46

Motivation (in C)

This is an extremely common pattern in programming: if (<action>) { if (!<next>) <rollback> <cleanup>}

For example: if (OpenDatabase()) { if (!WriteNameAndAge()) UnwriteNameAndAge(); CloseDatabase ();}

http://yaserzt.com/ 47

Motivation (in C+)

The “object-oriented” way might be: class RAII { RAII () {<action>} ~RAII () {<cleanup>}};

… RAII raii;try { <next>} catch (...) { <rollback> throw;}

http://yaserzt.com/ 48

Even More Motivation

What happens if you need to compose actions? if (<action1>) {

if (<action2>) {

if (!<next2>) {

<rollback2>

<rollback1>

}

<cleanup2>

} else

<rollback1>

<cleanup1>

}

http://yaserzt.com/ 49

The C++ Way (1/2)

What if we could write this: <action>SCOPE_EXIT {<cleanup>};SCOPE_FAIL {<rollback>};<next>

http://yaserzt.com/ 50

The C++ Way (2/2)

Extremely easy to compose: <action1>SCOPE_EXIT {<cleanup1>};SCOPE_FAIL {<rollback1>};<action2>SCOPE_EXIT {<cleanup2>};SCOPE_FAIL {<rollback2>};<next2>

http://yaserzt.com/ 51

ScopeGuard (1/6)

To start, we want some way to execute code when the execution is exiting the current scope.

The key idea here is to write a class that accepts a lambda at construction and calls it at destruction.

But how do we store a lambda for later use? We can use std::function, but should we?

http://yaserzt.com/ 52

ScopeGuard (2/6)

Let’s start like this:template <typename F>class ScopeGuard {public: ScopeGuard (F f) : m_f (std::move(f)) {} ~ScopeGuard () {m_f();}private: F m_f;}; And a helper function:template <typename F>ScopeGuard<F> MakeScopeGuard (F f) { return ScopeGuard<F>(std::move(f));}

http://yaserzt.com/ 53

ScopeGuard (3/6)

This is used like this:int * p = new int [1000];auto g = MakeScopeGuard([&]{delete[] p;});//… Without MakeScopeGuard(), we can’t construct ScopeGuard instances that use lambdas, because they don’t have type names.

But we don’t have a way to tell scope guard not to execute its clean-up code (in case we don’t want to roll back.)

http://yaserzt.com/ 54

ScopeGuard (4/6)

So we add a flag and a method to “dismiss” the scope guard when needed:template <typename F> class ScopeGuard {public: ScopeGuard (F f) : m_f (std::move(f)) , m_dismissed (false) {} ~ScopeGuard () {if (!m_dismissed) m_f();} void dismiss () {m_dismissed = true;}private: F m_f; bool m_dismissed;};

http://yaserzt.com/ 55

ScopeGuard (5/6)

A very important part is missing though… A move constructor:ScopeGuard (ScopeGuard && that) : m_f (std::move(that.m_f)) , m_dismissed (std::move(that.m_dismissed)){ that.dismiss ();} And we should disallow copying, etc.private: ScopeGuard (ScopeGuard const &); ScopeGuard & operator = (ScopeGuard const &);

http://yaserzt.com/ 56

ScopeGuard (6/6)

Our motivating example becomes:<action1>

auto g1 = MakeScopeGuard([&]{<cleanup1>});

auto g2 = MakeScopeGuard([&]{<rollback1>});

<action2>

auto g3 = MakeScopeGuard([&]{<cleanup2>});

auto g4 = MakeScopeGuard([&]{<rollback2>});

<next2>

g2.dismiss();g4.dismiss();

http://yaserzt.com/ 57

Variadic TemplatesDo you feel lucky?!

http://yaserzt.com/ 58

Variadic Templates (1/7)

Templates with variable number of arguments For exampletemplate <typename... Ts>size_t log (int severity, char const * msg, Ts&&... vs);

Remember the old way? size_t log (int severity, char const * msg, ...); Using va_list, va_start, va_arg and va_end in <cstdarg>

Or #define LOG_ERROR(msg, ...) \ log (SevError, msg, __VA_ARGS__)

http://yaserzt.com/ 59

Variadic Templates (2/7)

Almost the same for classes: template <typename... Ts> class ManyParents : Ts... { ManyParents () : Ts ()... {} }; Now these are valid: ManyParents<A> a; ManyParents<A, B> b;

http://yaserzt.com/ 60

Variadic Templates (3/7)

template <typename T, typename... PTs>T * Create (T * parent, PTs&&... ps){ T* ret = new T; ret->create (parent, std::forward<PTs>(ps)...); return ret;}

PTs and ps are not types, values, arrays, tuples or initializer lists.

They are new “things”.

http://yaserzt.com/ 61

Variadic Templates (4/7)

Rules of expansion are very interesting: Ts... → T1,T2,…,Tn Ts&&... → T1&&,…,Tn&& A<Ts,U>... → A<T1,U>,…,A<Tn,U> A<Ts,Us>... → A<T1,U1>,…,A<Tn,Un> f(42, vs...) → f(42,v1,…,vn) f(42, vs)... → f(42,v1),…,f(42,vn)

One more operation you can do:size_t items = sizeof...(Ts); // or vs

http://yaserzt.com/ 62

Variadic Templates (5/7)

Let’s implement the sizeof... operator as an example. template <typename... Ts> struct CountOf;

template <> struct CountOf<> { enum { value = 0 }; };

template <typename T, typename... Ts> struct CountOf { enum { value = CountOf<Ts...>::value + 1 }; }; Use CountOf like this: size_t items = CountOf<Ts>::value;

http://yaserzt.com/ 63

Variadic Templates (6/7)

Let’s implement a function named IsOneOf() that can be used like this: IsOneOf(42, 3, -1, 3.1416, 42.0f, 0)▪ which should return true

or IsOneOf (0, "hello")▪ which should fail to compile

How do we start the implementation? Remember, think recursively!

http://yaserzt.com/ 64

Variadic Templates (7/7)

template <typename A, typename T0>bool IsOneOf (A && a, T && t0){ return a == t0;}

template<typename A,typename T0,typename... Ts>bool IsOneOf (A && a, T0 && t0, Ts&&... ts){ return a == t0 || IsOneOf(a, std::forward<Ts>(ts)...);}

http://yaserzt.com/ 65

C++11 Memory ModelFinally!

http://yaserzt.com/ 66

What’s a Memory Model? (1/4)

The machine we code for (or want to code for): Each statement in your high-level program gets translated

into several machine instructions The (one) CPU runs the instructions in the program one by

one All interactions with memory finish before the next

instruction starts This is absolutely not true even in a single-threaded

program running on a single-CPU machine It hasn’t been true for about 2-3 decades now CPU technology, cache and memory systems and compiler

optimizations make it not true

http://yaserzt.com/ 67

What’s a Memory Model? (2/4)

Even in a multi-core world, we assume that: Each CPU runs the instructions one-by-one All interactions of each CPU with memory finish

before the next instruction starts on that CPU Memory ops from different CPUs are serialized by the

memory system and effected one before the other The whole system behaves as if we were executing

some “interleaving” of all threads as a single stream of operations on a single CPU

This is even less true (if that’s at all possible!)

http://yaserzt.com/ 68

What’s a Memory Model? (3/4)

YOUR COMPUTER DOES NOT EXECUTE THE PROGRAMS YOU WRITE. If it did, your programs would have been 10s or

100s of times slower It makes it appear as though your program is being

executed

http://yaserzt.com/ 69

What’s a Memory Model? (4/4)

The expected behavior of hardware with respect to shared data among threads of execution Obviously important for correctness Also important for optimization If you want to have the slightest chance to know

what the heck is going on!

http://yaserzt.com/ 70

Sequential Consistency

We have sequential consistency if: “the result of any execution is the same as if the

operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequence in the order specified by its program.”

E.g., if A, B, C are threads in a program, This is SC: A0, A1, B0, C0, C1, A2, C2, C3, C4, B1, A3

This is not: A0, A1, B0, C0, C2, A2, C1, C3, C4, B1, A3

http://yaserzt.com/ 71

Race Condition

You have a race condition if: A memory location can be simultaneously accessed

by two threads and at least one thread is a writer Memory location is defined as▪ Either a non-bitfield variable▪ Or a sequence of non-zero-length bitfields

“Simultaneously” is defined as▪ you can’t prove that one happens before the other

Remember that in case of a race condition in your code, anything can happen. Anything.

http://yaserzt.com/ 72

Optimizations (1/2)

Transformations (reorder, change, add, remove) to your code: Compiler: eliminate/combine subexprs, move code around, etc. Processor: execute your code out-of-order or speculatively, etc. Caches: delay your writes, poison or share data with each other, etc.

But you don’t care about all this. What you care about are: The code that you wrote The code that gets finally executed

You don’t (usually) care who did what; you only care that your correctly-synchronized program behaves as if some sequentially-consistent interleaving of the instructions (specially memory ops) of your threads is being executed. Also, all writes are visible atomically, globally, simultaneously

http://yaserzt.com/ 73

Optimizations (2/2)

Consider Peterson’s algorithm: (Both flags are atomic and initially zero)

Does this actually work?

Thread 1:

flag1 = 1; // (1)if (flag2 != 0)// (2) <resolve contention>else <in critical region?>

Thread 2:

flag2 = 1; // (3)if (flag1 != 0)// (4) <resolve contention>else <in critical region?>

http://yaserzt.com/ 74

What’s Our Memory Model?

The system (compiler, processor, memory) gives you sequentially-consistent execution, as long as your program is data-race free.

This is the memory model that C++11 (and C11) expect compilers and hardware to provide for the programmer.

The memory model is a contract between programmer and the system The programmer promises to correctly synchronize her program (no

race conditions) The system promises to provide the illusion that it is executing the

program you wrote

http://yaserzt.com/ 75

Acquire and Release (1/5)

Transaction: a logical op on related data that maintains an invariant Atomic: all or nothing Consistent: takes the system from one valid state to another Independent: correct in the presence of other transactions on the

same data Example: (We have two bank accounts: A and B)

Begin transaction (we acquire exclusivity)1. Add X units to account B

2. Subtract X units from account A

End transaction (we release exclusivity)

http://yaserzt.com/ 76

Acquire and Release (2/5)

Critical Region (or Critical Section): Code that must be executed in isolation from rest of program A tool that is used to implement transactions

E.g., you’d implement CR using a mutex like this:mutex MX; // MX is a mutex protecting X…{ lock_guard<mutex> lock (MX); // Acquire <read/write X>} // Release Same principle using atomic variables, etc.

http://yaserzt.com/ 77

Acquire and Release (3/5)

Important rule: code can’t move out of a CR E.g., if you have:

MX.lock (); // Acquirex = 42;MX.unlock (); // Release

The system can’t transform it to: x = 42;MX.lock (); // AcquireMX.unlock (); // Release

MX.lock (); // AcquireMX.unlock (); // Releasex = 42;

http://yaserzt.com/ 78

Acquire and Release (4/5)

If we have: x = 7;M.lock();y = 42;M.unlock();z = 0;

Which of these can/can’t be done?

M.lock();x = 7;y = 42;z = 0;M.unlock();

M.lock();z = 0;y = 42;x = 7;M.unlock();

z = 0;M.lock();y = 42;M.unlock();x = 7;

http://yaserzt.com/ 79

Acquire and Release (5/5)

A pattern emerges! For SC acquire/release: You can’t move things up across an acquire. You can’t move things down across a release. You can’t move an acquire up across a release.

Acquire and release are also called one-way barriers (or one-way fences.)

A release store makes its prior accesses visible to an acquire load that sees (pairs with) that store. Important: a release pairs with an acquire in another thread.

A mutex lock or loading from an atomic variable is an acquire. A mutex unlock or storing to an atomic variable is a release.

http://yaserzt.com/ 80

Atomics, Threads and SynchronizationWeapons of Mass Destruction

http://yaserzt.com/ 81

Atomics in C++

Defined in header <atomic> Use like std::atomic<T> x;

E.g, std::atomic<int> ai; or std::atomic_int ai; or, std::atomic<std::string> as;

Might use locks (spinlocks) under the hood. Check with x.is_lock_free()

No operation works on two atomics at once or return an atomic. Available ops are =, T, ++, --, +=, -=, &=, |=, ^= There is also:

T exchange (T desired, …) bool compare_exchange_strong (T& expected, T desired, …) bool compare_exchange_weak (T& expected, T desired, …)

You can also use std::atomic_flag which has test_and_set(…) and clear(…). (And don’t forget ATOMIC_FLAG_INIT.)

http://yaserzt.com/ 82

Threads in C++ (1/2)

Represented by class std::thread (in header <thread>) default-constructible and movable (not copyable) template <class F, class... Args>explicit thread (F&& f, Args&&... args);

Should always call join() or detach() t.join() waits for thread t to finish its execution t.detach() detaches t from the actual running thread otherwise the destructor will terminate the program

Get information about a thread object using std::thread::id get_id () bool joinable ()

http://yaserzt.com/ 83

Threads in C++ (2/2)

The static function unsigned std::thread::hardware_concurrency() returns the number of threads that the hardware can run concurrently

There is also a namespace std::this_thread with these members: std::thread::id get_id () void yield () void sleep_for (<duration>) void sleep_until (<when>)

http://yaserzt.com/ 84

Mutexes and Locks

There are four types of mutexes in C++ (in header <mutex>) mutex: basic mutual exclusion device timed_mutex: provides locking with a timeout recursive_mutex: can be acquired more than once by the same thread recursive_timed_mutex

They all provide lock(), unlock() and bool try_lock() The timed versions provide bool try_lock_for (<duration>)

and bool try_lock_until (<when>) Generally, you want to use a std::lock_guard<MT> to

lock/unlock the mutex Locks the mutex on construction; unlocks on destruction

http://yaserzt.com/ 85

Once!

It is not uncommon to need to do something once and exactly once, e.g., initialization of some state, setting up of some resource, etc.

Multiple threads might attempt this, because they need the result of the initialization, setup, etc.

You can use (from header <mutex>) template <typename F, typename... Args>

void call_once (std::once_flag & flag, F && f, Args&& args...);

Like this: (remember that it also acts as a barrier)std::once_flag init_done;void ThreadProc () { std::call_once (init_done, []{InitSystem();}); <rest of the procedure>}

http://yaserzt.com/ 86

async and future

async() can be used to run functions asynchronously (from header <future>) template <typename F, typename... Args>std::future<RetTypeOfF> async (F && f, Args&&... args);

returns immediately, but runs f(args...) asynchronously (possibly on another thread)

e.g. future<int> t0 = async(FindMin, v); or future<int> t1 = async([&]{return FindMin(v);});

An object of type std::future<T> basically means that someone has promised to put a T in there in the future. Incidentally, the other half of future<T> is called promise<T> Key operation is T get (), which waits for the promised value.

http://yaserzt.com/ 87

Using async and future

#include <future>string flip (string s) { reverse (s.begin(), s.end()); return s;}int main () { vector<future<string>> v; v.push_back (async ([] {return flip( " ,olleH");})); v.push_back (async ([] {return flip(" weN evarB");})); v.push_back (async ([] {return flip( "!dlroW");})); for (auto& i : v) cout << i.get(); cout << endl; return 0;}

http://yaserzt.com/ 88

Optional<T>Really Getting Rid of NULL

http://yaserzt.com/ 89

Motivation

What do you do when you have char const * get_object_name (int id), and the object

ID does not exist in your objects? unsigned get_file_size (char const * path), and

the file does not exist? double sqrt (double x), and x is negative?

You might use NULL, or “special error values” or even exceptions, but the fact remains that sometimes, you don’t want to return (or pass around) anything. You want some values to be “optional”. Aha!

Let’s write a class that allows us to work with such values…

http://yaserzt.com/ 90

End of Day 2Any questions?

http://yaserzt.com/ 91

Stuff We Couldn’t Get To

Implementation of Optional<T> Discussion of wrapping objects with locking General wrapping of asynchronous transactions Initializer lists and uniform initialization constexpr std::unordered_containers Smart pointers

std::unique_ptr std::shared_ptr

Implementing shared pointer

http://yaserzt.com/ 92

If you write C-style code, you’ll end up with C-style bugs.

-- Bjarne Stroustrup

If you write Java-style code, you’ll have Java-level performance.

Any more questions?

Contact us at http://deadmage.com/And me at yaserzt@gmail.com

top related