项目作者: pvthuyet

项目描述 :
Concurrency with modern C++
高级语言: C++
项目地址: git://github.com/pvthuyet/Concurrency_With_Modern_Cpp.git
创建时间: 2019-08-01T12:20:38Z
项目社区:https://github.com/pvthuyet/Concurrency_With_Modern_Cpp

开源协议:

下载


C++20 Features

Lambda expression

  1. [ captures ] <tparams> ( params ) specifiers exception attr -> ret requires { body }
  • attr: have not supported yet (test on MSVC 16.8 preview)
  • requires: have not supported yet (test on MSVC 16.8 preview)

    Example
    1. struct Foo {
    2. template<class ...Args> constexpr Foo(Args&& ...args) noexcept { print(std::forward<Args>(args)...); }
    3. template<class T, class ...Args> constexpr void print(T&& t, Args&&... args) const noexcept {
    4. std::cout << t;
    5. auto coutSpaceAndArg = [](auto&& arg) { std::cout << ' ' << arg; };
    6. (..., coutSpaceAndArg(std::forward<Args>(args))); // fold expression since C++17
    7. }
    8. };
    9. int main() {
    10. int x = 10;
    11. auto lamb = [x]<typename ...Ts>(Ts&&... ts) mutable constexpr noexcept -> auto {
    12. Foo(std::forward<Ts>(ts)...);
    13. return ++x;
    14. };
    15. }

    Code generated by complier

    1. struct Foo {
    2. template<class ... Args> inline constexpr Foo(Args &&... args) noexcept { print(std::forward<Args>(args)... );}
    3. template<class T> inline constexpr void print(T && t) const noexcept { std::cout << t; }
    4. template<class T, class ... Args> inline constexpr void print(T && t, Args &&... args) const noexcept {
    5. print(std::forward<T>(t));
    6. if constexpr(sizeof...(Args) > 0) {
    7. std::operator<<(std::cout, ' ');
    8. print(std::forward<Args>(args)... );
    9. }
    10. }
    11. };
    12. int main() {
    13. int x = 10;
    14. class __lambda_27_17 {
    15. public:
    16. template<typename ... Ts>
    17. inline /*constexpr */ auto operator()(Ts &&... ts) noexcept {
    18. Foo(std::forward<Ts>(ts)... );
    19. return ++x;
    20. }
    21. private:
    22. int x;
    23. public:
    24. __lambda_27_17(int & _x)
    25. : x{_x}
    26. {}
    27. };
    28. __lambda_27_17 lamb = __lambda_27_17{x};
    29. }
    Recursive lambda

    ```
    template
    struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template
    decltype(auto) operator()(Args&&… args) const {

    1. // we pass ourselves to f, then the arguments.
    2. // the lambda should take the first argument as `auto&& recurse` or similar.
    3. return f(*this, std::forward<Args>(args)...);

    }
    };
    // helper function that deduces the type of the lambda:
    template
    y_combinator> make_y_combinator(F&& f) {
    return { std::forward(f) };
    }

void test() {
auto fac = make_y_combinator( -> int{
if (n <= 1) return 1;
return n * recurse(n - 1);
});
std::cout << fac(5) << std::endl;
}

  1. ## X. C++ notes
  2. * [The rule of three/five/zero](https://en.cppreference.com/w/cpp/language/rule_of_three)
  3. # C++17 Features
  4. ### [Polymorphic Memory Resource](https://github.com/pvthuyet/Concurrency_With_Modern_Cpp/blob/master/pmr.md)
  5. ### Part I: Basic Language Features
  6. #### 1. Structured Bindings
  1. struct MyStruct {
  2. int i = 0;
  3. std::string s;
  4. };
  5. MyStruct ms;
  6. auto [u,v] = ms; // as copy
  7. auto& [k,l] = ms; // as reference
  8. ...
  9. std::unordered_map myMap;
  10. for(const auto& [key, val] : myMap) {
  11. std::cout << key ": " << val << '\n';
  12. }
  1. #### 2. `If` and `switch` with initialization
  2. #### 3. `inline` variables
  3. Since C++17 you can define a variable/object in a header file as `inline` and if this definition is used by multiple translation units, they all refer to `the same unique object`

class MyClass {
static inline std::string name = “”; // OK since C++17
};
inline MyClass myGlobalObj; // OK even if included/defined by multiple CPP files

  1. * `constexpr` now implies `inline` for `static` data member

struct D {
static constexpr int n = 5;
//inline static constexpr int n = 5; // the same as above
}

  1. #### 4. Aggregate extensions
  2. #### 5. Mandatory copy elision or passing unmaterialized objects
  3. `copy elision` benefit:
  4. * Improve performance
  5. * Apply for not `CopyConstructible` object.
  6. ##### a. Think about below:
  7. below codes can't compile before C++17

std::atomic_int getValue() {
return std::atomic_int{1}; // copy Elision (Mandatory since C++17)
}

class Foo {
};
Foo getFoo() {
return Foo(); // copy Elision (Mandatory since C++17)
}
Foo getFoo() {
Foo fo;
return fo; // NRVO: move constructor
}
Foo getFoo(const Foo& fo) {
return fo; // Copy constructor
}
Foo getFoo(Foo fo) {
return fo; // Move constructor
}

  1. ##### b. Value Categories
  2. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/value.png)
  3. **lvalue**
  4. **prvalue**: pure rvalue
  5. **xvalue**: expiring value
  6. **glvalue**: generalized lvalue
  7. * All names used as expressions are `lvalues`.
  8. * All string literals used as expression are `lvalues`.
  9. * All other literals (`4.2`, `true`, or `nullptr`) are `prvalues`.
  10. * All temporaries (especially objects returned by value) are `prvalues`.
  11. * The result of `std::move()` is an `xvalue`.

class X {
};
X v;
const X c;
void f(const X&); // accepts an expression of any value category
void f(X&&); // accepts prvalues and xvalues only, but is a better match

f(v); // passes a modifiable lvalue to the first f()
f(c); // passes a non-modifiable lvalue to the first f()
f(X()); // passes a prvalue to the second f()
f(std::move(v)); // passes an xvalue to the second f()

  1. **C++17 then introduces a new term, called `materialization` (of a temporary) for the moment a `prvalue` becomes a temporary object.
  2. Thus, a `temporary materialization conversion` is a `prvalue` to `xvalue` conversion.**

void f(const X& p); // accepts an expression of any value category but expects a glvalue

f(X()); // passes a prvalue materialized as xvalue

  1. #### 6. Lambda extensions
  2. * **`constexpr` Lambdas**: only literal types, no `static variables`, no virtual`, no try/catch`, no new/delete`

auto squared = { // implicitly constexpr since C++17
return valval;
};
auto squared = constexpr { // explicitly constexpr since C++17
return val
val;
};

  1. * **Passing Copies of `this` to Lambdas**
  2. If wed have captured this with `[this], [=], or [&],` the thread runs into undefined behavior.

class C {
private:
std::string name;
public:
void foo() {
auto var = [*this] { std::cout << name << ‘\n’; };
}
};

  1. #### 7. New attributes and attribute features
  2. * Attribute `[[nodiscard]]`
  3. * Attribute `[[maybe_unused]]`
  4. * Attribute `[[fallthrough]]`
  5. #### 8. Other Language Features
  6. ##### a. Nested Namespaces

namespace A::B::C {}

  1. #### b. Defined Expression Evaluation Order
  2. #### c. Relaxed Enum Initialization from Integral Values
  3. #### d. Fixed Direct List Initialization with `auto`
  4. **The recommended way to initialize variables and objects should always be to use direct list initialization `(brace initialization without =)`.**

auto a{42}; // initializes an int now
auto c = {42}; // still initializes a std::initializer_list

  1. #### e. Hexadecimal Floating-Point Literals
  2. #### f. UTF-8 Character Literals
  3. #### g. Exception Specifications as Part of the Type

void f1();
void f2() noexcept; // different type

  1. * Using Conditional Exception Specifications
  2. * Consequences for Generic Libraries
  3. #### h. Single-Argument `static_assert`
  4. #### i. Preprocessor `Condition __has_include`

if __has_include()

include

endif

  1. ### Part III: New Library Components
  2. #### 1. `std::optional<>`
  3. `std::optional<>` model a nullable instance of an arbitrary type.
  4. The instance might be a member, an argument, or a return value.
  5. You could also argue that a `std::optional<>` is a container for zero or one element.
  6. * Optional Return Values

// convert string to int if possible:
std::optional asInt(const std::string& s) {
std::optional ret; // initially no value
try {
ret = std::stoi(s);
}
catch (…) {
}
return ret;
}

  1. * Optional Arguments and Data Members

class Name {
std::string mFirst;
std::optional mMid;
std::string mLast;
}

  1. #### 2. `std::variant<>`
  2. With std::variant<> the C++ standard library provides a new `union class`, which among other benefits supports a new way of polymorphism and dealing with inhomogeneous collections.
  3. * Using `std::variant<>`

std::variant var{“hi”}; // initialized with string alternative
std::cout << var.index(); // prints 1
var = 42; // now holds int alternative
std::cout << var.index(); // prints 0

  1. * std::monostate
  2. To support variants, where the first type has no default constructor, a special helper type is provided: `std::monostate`.

std::variant v2; // OK

  1. * Visitors

std::variant var(42);
auto printvariant = {
std::cout << val;
};
std::visit(printvariant, var);

  1. * Polymorphism and Inhomogeneous Collections with `std::variant`

// common type of all geometric object types:
using GeoObj = std::variant;
// create and initialize a collection of geometric objects:
std::vector createFigure(){
std::vector f;
f.push_back(Line{Coord{1,2},Coord{3,4}});
f.push_back(Circle{Coord{5,5},2});
f.push_back(Rectangle{Coord{3,3},Coord{6,4}});
return f;
}
int main() {
std::vector figure = createFigure();
for (const GeoObj& geoobj : figure) {
std::visit([] (const auto& obj) {
obj.draw(); // polymorphic call of draw()
}, geoobj);
}
}

  1. * In general, I would recommend now to program polymorphism with `std::variant<>` by default, because it is usually faster (no new and delete, no virtual functions for non-polymorphic use), a lot safer (no pointers), and usually all types are known at compile-time of all code.
  2. #### 3. `std::any`
  3. * Using `std::any`

std::any a; // a is empty
std::any b = 4.3; // b has value 4.3 of type double
a = 42; // a has value 42 of type int
b = std::string{“hi”}; // b has value “hi” of type std::string

  1. #### 4. `std::byte`
  2. * Using std::byte

std::byte b1{0x3F};
std::byte b2{0b1111’0000};
std::byte b3[4] {b1, b2, std::byte{1}}; // 4 bytes (last is 0)

  1. #### 5. `String Views`
  2. `The class template basic_string_view describes an object that can refer to a constant contiguous sequence of char-like objects`
  3. A string_view doesn't manage the storage that it refer to. Lifetime management is up to the user.
  4. * When would you use a `string_view` instead of `string`
  5. Pass as a parameter to a `pure` function(parameters `const string&`)
  6. Returning from a function
  7. A reference to part of long-lived data structure.
  8. * Drawbacks of string_view
  9. Lifetime management
  10. not `null-terminated`
  11. **Don’t use `std::string_view` at all unless you know what you do.**
  12. * Don't use `std::string_view` to initialize a `std::string` member
  13. * Don't use initialize a `string_view` as below

std::string getStr() {
return std::string(“long_string_help_to_detect_issues”);
}
std::string_view sv1 = getStr(); // RISK
string_view sv = “abc”s; // RISK

  1. #### 6. The Filesystem Library
  2. ...
  3. ### Part III: Expert Utilities
  4. * `std::from_chars`: converts a given character sequence to a numeric value
  5. * `std::to_chars`: converts numeric values to a given character sequence
  6. # C++11 Features
  7. ### 1. Smart Pointers
  8. - `std::unique_ptr`, `std::make_unique`
  9. - `std::shared_ptr`, `std::make_shared`
  10. - `std::weak_ptr`
  11. ### 2. Rvalue references, Move Semantics, and Perfect Forwarding
  12. - `lvalue`: correspond to objects you can refer to, either by name or by following a pointer or lvalue reference.
  13. - `rvalue`: correspond to temporary objects returned from functions.
  14. - `std::move`: performs an unconditionally casts its input into an rvalue reference. It doesn't move anything.
  15. - `std::forward`: casts its argument to an rvalue only if that argument is bound to an rvalue. It doesn't forward anything.
  16. [The Nightmare of Move Semantics for Trivial Classes](https://www.youtube.com/watch?v=PNRju6_yn3o&list=PLKtBMOPB5ra9DeN_N6jEDg0eY07_sgTtk&index=7&t=10s)
  17. [Code example](https://github.com/pvthuyet/Concurrency_With_Modern_Cpp/blob/master/universalreference/Customer.h)
  18. ### 3. Lambda Expressions
  19. * Caputure local variable only, no member variables, no static variables.
  20. * Avoid default capture modes
  21. There are two default capture modes in C++11: by-reference `[&]`, and by-value `[=]`
  22. Default by-reference capture can lead to dangling reference
  23. Default by-value capture is susceptible to dangling pointers(especially this), and it misleadingly suggests that lambdas are self-contained.
  24. * C++14 supported caputure by moving the object (C++11 can use std::bind but a litle complicated)
  1. std::vector<double> data;
  2. auto func = [data = std::move(data)]{
  3. // do something
  4. };
  1. ### 4. Concurrency API
  2. - `std::thread`
  3. - `std::async`
  4. - `std::future`
  5. - `std::promise`
  6. - `std::atomic`
  7. - `std::mutex`
  8. - `std::condition_variable`
  9. ### 5. Variadic templates
  10. ### 6. New containers
  11. - [`std::tuple`](https://en.cppreference.com/w/cpp/utility/tuple)
  12. - [`std::array`](https://en.cppreference.com/w/cpp/container/array)
  13. - [`std::forward_list`](https://en.cppreference.com/w/cpp/container/forward_list)
  14. - [`std::unordered_set`](https://en.cppreference.com/w/cpp/container/unordered_set)
  15. - [`std::unordered_map`](https://en.cppreference.com/w/cpp/container/unordered_map)
  16. - [`std::unordered_multiset`](https://en.cppreference.com/w/cpp/container/unordered_multiset)
  17. - [`std::unordered_multimap`](https://en.cppreference.com/w/cpp/container/unordered_multimap)
  18. ### 7. [Utilities library](https://en.cppreference.com/w/cpp/utility)
  19. - [`bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
  20. - [`std::move_if_noexcept`](https://en.cppreference.com/w/cpp/utility/move_if_noexcept)
  21. - [`std:: initializer_list`](https://en.cppreference.com/w/cpp/utility/initializer_list)
  22. - [`std::hash`](https://en.cppreference.com/w/cpp/utility/hash)
  23. - [`std::regex`](https://en.cppreference.com/w/cpp/regex)
  24. - [`std::declval`](https://en.cppreference.com/w/cpp/utility/declval)
  25. ### 8. New Keywords
  26. | New Keyword | Explain |
  27. | :-------------- | --------------------------- |
  28. | `delete` | to prevent users call a particular function |
  29. | `default` | |
  30. | `override` | |
  31. | `final` | |
  32. | `noexcept` | |
  33. | `auto` | |
  34. | `constexpr` | |
  35. | `nullptr` | |
  36. | `thread_local` | auto lb = [](int n) { static thread_local int v = 0; v += n;}; |
  37. | `using` alias | |
  38. | `decltype` | |
  39. | `enum class` | |
  40. | `static_cast` | conversion between similar types such as pointer types or numeric types |
  41. | `const_cast` | adds or removes const or volatile |
  42. | `reinterpret_cast` | converts between pointers or between integral types and pointers |
  43. | `dynamic_cast` | converts between polymorph pointers or references in the same class hierarchy |
  44. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/timeline.png)
  45. ## VII. Moving to Modern C++ [by Scott Meyers](http://shop.oreilly.com/product/0636920033707.do?cmp=af-code-books-video-product_cj_0636920033707_7708709)
  46. #### 1. Distinguish between `()` and `{}` when creating objects
  47. * Braced `{}` initialization is the most widely usable initialization syntax, it prevents narrowing conversions, and its immune to C++’s most vexing parse.
  48. * During constructor overload resolution, braced initializers are matched to `std::initializer_list` parameters if at all possible, even if other constructors offer seemingly better matches.
  49. * An example of where the choice between parentheses and braces can make a significant difference is creating a `std::vector<numeric type>` with two arguments.
  50. * Choosing between parentheses and braces for object creation inside templates can be challenging.
  51. #### 2. Prefer `nullptr` to `0` and `NULL`.
  52. #### 3. Prefer `alias declarations` to `typedefs`.
  53. #### 4. Prefer `scoped enums` to `unscoped enums`.
  54. #### 5. Prefer `deleted` functions to `private` undefined ones
  55. #### 6. Declare `overriding` functions `override`.
  56. #### 7. Prefer `const_iterators` to `iterators`.
  57. #### 8. Declare functions `noexcept` if they won’t emit exceptions
  58. #### 9. Use `constexpr` whenever possible.
  59. #### 10. Make `const` member functions thread safe.
  60. #### 11. Understand special member function generation
  61. * Default constructor
  62. * Destructor
  63. * Copy constructor
  64. * Copy assignment operator
  65. * Move constructor and move assignment operator
  66. #### 12. Prefer `task-based` programming to `thread-based`
  67. #### 13. Specify `std::launch::async` if asynchronicity is essential
  68. #### 14. Make `std::threads` unjoinable on all paths
  69. #### 15. Be aware of varying thread handle destructor behavior
  70. #### 16. Consider void `futures` for one-shot event communication
  71. #### 17. Use `std::atomic` for concurrency, `volatile` for special memory.
  72. #### 18. Consider pass by value for copyable parameters that are cheap to move and always copied
  73. #### 19. Consider `emplacement` instead of `insertion`
  74. ## VI. Smart Pointer
  75. Refer to [C++ Smart Pointers - Usage and Secrets - Nicolai Josuttis](https://www.youtube.com/watch?v=XH4xIyS9B2I&t=1336s)
  76. ### 1. std::unique_ptr
  77. * Use std::unique_ptr for exclusive-ownership resource management
  78. * Downcasts do not work for unique pointers
  1. class GeoObj {};
  2. class Circle : public GeoObj {};
  3. std::vector< std::unique_ptr<GeoObj> > geoObjs;
  4. geoObjs.emplace_back( std::make_unique<Circle>(...) ); // Ok, insert circle into collection
  5. const auto& p = geoObjs[0]; // p is unique_ptr<GeoObj>;
  6. std::unique_ptr<Circle> cp{p}; // Compile-time Error
  7. auto cp(dynamic_cast<std::unique_ptr<Circle>>(p); // Compile-time Error
  8. if (auto cp = dynamic_cast<Circle*>(p.get())) // Ok, use in `if` because of restrict life time
  9. {
  10. // use cp as Circle*
  11. }
  1. * Pass std::unique_ptr to function

void sink(std::unique_ptr const& up){} // Ok as normal, reference to unique pointer
void sink(std::unique_ptr up){} // Ok, pass value only accept move Herb Sutter style
void sink(std::unique_ptr&& up){} // Ok, pass rvalue only accept move Scott Meyers style

auto up(std::make_unique());
sink(std::move(up));
up.release(); // Remember destroy up after using std::move

  1. * Custome Deleter
  1. auto deleter = [](std::FILE* fp) {
  2. std::fclose(fp);
  3. fp = nullptr;
  4. };
  5. std::FILE* fp = nullptr;
  6. fopen_s(&fp, "test.txt", "w+");
  7. std::unique_ptr<std::FILE, decltype(deleter)> up(fp, deleter); // deleter as type and argument
  8. //std::unique_ptr<std::FILE, decltype(deleter)> up(fp); // OK with c++20
  9. const char str[] = "hello world!\n";
  10. fwrite(str, sizeof(char), sizeof(str), up.get());
  1. ### 2. std::shared_ptr
  2. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/sharedptr.png)
  3. #### a. Use std::shared_ptr for shared-owenership resource management
  4. * std::shared_ptrs are twice the size of a raw pointer
  5. * Memory for the reference count must be dynamically allocated
  6. * Increments and decrements of the reference count must be atomic
  7. #### b. Custom deleter
  1. auto deleter = [](Widget* pw) {
  2. delete pw;
  3. pw = nullptr;
  4. };
  5. std::unique_ptr<Widget, decltype(deleter)> upw(new Widget, deleter); // deleter as type and argument
  6. std::shared_ptr<Widget> spw(new Widget, deleter); // deleter is only at arg
  1. #### c. [Cast APIs for std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast)
  2. * std::static_pointer_cast
  3. * std::dynamic_pointer_cast
  4. * std::const_pointer_cast
  5. * std::reinterpret_pointer_cast
  6. #### d. Duplicate `Control block` Issue
  7. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/sharedptrerr1.png)
  8. * First exmaple:
  1. int* p = new int;
  2. std::shared_ptr<int> sp1(p);
  3. std::shared_ptr<int> sp2(p);
  1. **(*)Node** : *create a std::shared_ptr from raw pointer is bad idea*
  2. * Second exmaple:
  3. Suppose our program uses `std::shared_ptrs` to manage Widget objects, and we have a data structure that keeps track of Widgets that have been processed.
  1. class Widget {
  2. public:
  3. void process(std::vector< std::shared_ptr<Widget> > &widgets) {
  4. widgets.emplace_back(this); // Create new control-block and point to the same Widget object.
  5. }
  6. }
  7. int main() {
  8. std::vector< std::shared_ptr<Widget> > processedWidgets;
  9. auto spw = std::make_shared<Widget>(); // Create a control-block and point to new Widget object
  10. spw->process(processedWidgets);
  11. }
  1. The output of progam
  2. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/sharedptrdup.png)
  3. * The problem is there is 2 control-blocks point to the same Widget object. Hence, the Widget object was destroyed 2 times-> Crash application.
  4. ##### **Solution** by [Scott Meyers](https://www.oreilly.com/library/view/effective-modern-c/9781491908419/)
  5. * Use `std::enable_shared_from_this`
  6. * This is [The Curiously Recurring Template Pattern(CRTP)](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
  1. class Widget : public std::enable_shared_from_this<Widget> {
  2. public:
  3. void process(std::vector< std::shared_ptr<Widget> > &widgets) {
  4. widgets.emplace_back(shared_from_this()); // Look-up control-block
  5. }
  6. }
  7. int main() {
  8. std::vector< std::shared_ptr<Widget> > processedWidgets;
  9. auto spw = std::make_shared<Widget>();
  10. spw->process(processedWidgets);
  11. }
  1. Ok, so far so good, `shared_from_this` looks up the control block for the current object, and it creates a new std::shared_ptr tha refers to that control block.
  2. But in case the control block has not existed, `shared_from_this` will throw exception. For example if change `auto spw = std::make_shared<Widget>();` -> `Widget* spw = new Widget();`
  3. Therefore, we must make sure the `std::shared_ptr` already existed before call `process()`
  4. Apply the `factory function` template
  1. class Widget : public std::enable_shared_from_this<Widget> {
  2. private:
  3. Widget() = default; // Invisiable constructor
  4. public:
  5. template<typename... Args>
  6. static std::shared_ptr<Widget> create(Args&&... params) {
  7. return std::shared_ptr<Widget>(new Widget(std::forward<Args>(params)...));
  8. }
  9. void process(std::vector< std::shared_ptr<Widget> > &widgets) {
  10. widgets.emplace_back(shared_from_this()); // Look-up control-block
  11. }
  12. }
  13. int main() {
  14. std::vector< std::shared_ptr<Widget> > processedWidgets;
  15. auto spw = Widget::create();
  16. spw->process(processedWidgets);
  17. }
  1. **OK so far so good, the issue was sorted**
  2. #### e. std::shared_ptr overhead
  3. There is overhead if sending std::shared_ptr value to much (pass by argument value of function)
  4. *Should pass by reference of std::shared_ptr*
  5. #### f. Are std::shared_ptr, std::weak_ptr thread-safe??
  6. [Refer to Rainer Grimm](https://www.modernescpp.com/index.php/atomic-smart-pointers)
  7. A `std::shared_ptr` consists of a control block and its resource:
  8. * The control block is thread-safe: That means, modifying the reference counter is an atomic operation and you have the guarantee that the resource will be deleted exactly once.
  9. * The access to the resource is not thread-safe.
  10. * [Atomic smart pointers support by C++20](https://en.cppreference.com/w/cpp/experimental/atomic_shared_ptr)
  11. #### g. Leak memory
  12. * This is circlic references issue of std::shared_ptr
  13. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/sharedptrleak_.png)
  14. In this case, we cannot use raw pointer because if A is destroyed, B will contain a pointer to A that will dangle.
  15. B won't be able to detect that, so B may in advertently dereference the dangling pointers.
  16. Thank to `std::weak_ptr`
  17. ### 3. std::weak_ptr
  18. * Allow to share but not own an object or resource
  19. * `Pointer that knows when it dangles`(Scott Meyers)
  20. * Resolve the circlic reference issue of `std::shared_ptr` and `raw pointer`
  21. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/sharedptrleakfix.png)
  22. * Potential use cases for std::weak_ptr include caching, observer lists.
  23. * Using a std::weak_ptr
  1. 1. No pointer interface
  2. 2. Access to the resource through a temporary shared pointer
  3. std::weak_ptr<R> wp;
  4. if (auto p = wp.lock())
  5. p->callMember()
  1. * But be aware that `std::weak_ptr` might hold memory if use with `std::make_shared<R>` to create `td::shared_ptr`
  2. ## V. Atomic
  3. Refer [Fedor Pikus talked](https://www.youtube.com/watch?v=ZQFzMfHIxng&list=PLKtBMOPB5ra9DeN_N6jEDg0eY07_sgTtk&index=6&t=3143s)
  4. * std::atomic is neither copyable nor movable. (**Atomic variables are `not CopyConstructible`**)
  5. * The primary std::atomic template may be instantiated with any `TriviallyCopyable` type T satisfying both `CopyConstructible` and `CopyAssignable`.
  6. What is trivially copyable?
  1. 1. Continuous chunk of memory
  2. 2. Copying the object means copying all bits (memcpy)
  3. 3. No virtual function, noexcept constructor
  1. * On MSVC: If not define `_ENABLE_ATOMIC_ALIGNMENT_FIX`, the compiler will complain: `std::atomic<T>` with sizeof(T) equal to 2/4/8 and `alignof(T)` < `sizeof(T)`

struct Data { // user-defined trivially-copyable type
int x; // 4 byte
void* ptr; // 4 byte
Data() noexcept : x(0), ptr(nullptr)
{}
};
std::atomic atm;

struct A { int a[100]; };
std::atomic a;
assert(std::atomic_is_lock_free(&a)); // false: a is not lock-free

  1. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/atomic.png)
  2. (*) **Note that doesn't full support for `std::atomic<float>`, `std::atomic<double>` until C++20**
  3. ## IV. Memory Model
  4. Before C++11, there was only one contract. The C++ language specification did not include multithreading or atomics. There was no memory model.
  5. With C++11 everything has changed. C++11 is the first standard aware of multiple threads. The reason for the well-defined behaviour of threads is the C++ memory model that was heavily inspired by the [Java memory model](https://en.wikipedia.org/wiki/Java_memory_model)

enum memory_order{
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
}

  1. * Read operation: `memory_order_acquire` and `memory_order_consume`
  2. * Write operation: `memory_order_release`
  3. * Read-modify-write operation: `memory_order_acq_rel` and `memory_order_seq_cst`
  4. * Relaxed operation: `memory_order_relaxed`, there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed.
  5. ![1](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/memorymodel_.png)
  6. ![2](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/expertlevel_.png)
  7. #### 1. Strong Memory Model (refer to sequential consistency semantic `memory_order_seq_cst`)
  8. Sequential consistency provides two guarantees:
  9. * The instructions of a program are executed in source code order.
  10. * There is a global order of all operations on all threads.
  11. ![1](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/strongmemorymodel.png)
  12. #### 2. Weak Memory Model (refer to relaxed semantic `memory_order_relaxed`)
  13. * The counter-intuitive behaviour is that thread 1 can see the operations of thread 2 in a different orderr, so there is no view of a global clock.
  14. Fro example, from the perspective of thread 1, it is possible that the operation `res2= x.load()` overtakes `y.store(1)`.
  15. * It is even possible that thread 1 or thread 2 do not perform their operations in the order defined in the source code.
  16. For example, thread 2 can first execute `res2= x.load()` and then `y.store(1)`.
  17. This quite very very difficult to understand
  18. [cppreference](https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering)
  19. [disscus](https://stackoverflow.com/questions/35648936/reordering-and-memory-order-relaxed)
  20. ## III. Multithreading
  21. #### 1. Threads
  22. #### 2. Shared Data
  23. * **Mutexes**
  24. `std::mutex`
  25. `std::recursive_mutex`: allows the same thread to lock the mutex many times.
  26. `std::timed_mutex`
  27. `std::recursive_timed_mutex`
  28. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/mutexes.png)
  29. `shared_mutex`: Shared mutexes are usually used in situations when multiple readers can access the same resource at the same time without causing data races, but only one writer can do so.
  30. `shared_timed_mutex`
  31. * **Locks**
  32. `std::lock_guard`
  33. `std::unique_lock`
  34. `std::scoped_lock` : lock many mutexes
  35. `std::shared_lock` : many threads can read but only one thread can write
  36. #### 3. Thread-Local Data
  37. Thread-local data, also known as thread-local storage, is created for each thread separately.
  38. #### 4. Condition Variables
  39. `std::condition_variable`: only wait on object of type `std::unique_lock<std::mutex>`
  40. `std::condition_variable_any`: can wait on an user-supplied lock type that meet the concept of `BasicLockable`.
  41. Condition variables enable threads to be synchronised via messages.
  42. * The Predicate
  43. * Lost Wakeup
  44. * Spurious Wakeup
  45. * The Wait Workflow

std::uniquelock lck(mutex);
condVar.wait(lck, []{ return dataReady; });

  1. Equal

std::uniquelock lck(mutex);
while ( ![]{ return dataReady; }() {
condVar.wait(lck);
}

  1. Even if the shared variable is atomic, it must be modified under the mutex to publish the modification to the waiting thread correctly.
  2. **Use a mutex to protect the shared variable**
  3. Even if you make dataReady an atomic, it must be modified under the mutex;
  4. if not the modification to the waiting thread may be published, but not correctly synchronised.
  5. This race condition may cause a deadlock.
  6. What does that mean: published, but not correctly synchronised.
  7. Lets have once more a closer look at the wait workflow and assume that `deadReady` is an atomic and is modified not protected by the mutex mutex_.

std::uniquelock lck(mutex);
while ( ![]{ return dataReady.load(); }() {
// time window
condVar.wait(lck);
}

  1. Let me assume the notification is send while the condition variable condVar is not in the waiting state.
  2. This means execution of the thread is in the source snippet between line 2 and 4 (see the comment time window).
  3. The result is that the notification is lost.
  4. Afterwards the thread goes back in the waiting state and presumably sleeps forever.
  5. This wouldnt have happened if dataReady had been protected by a mutex.
  6. Because of the synchronisation with the mutex, the notification would only be sent if the condition variable the notification would only be sent if the condition variable
  7. #### 5. Tasks
  8. ![1](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/task.png)
  9. * **Tasks versus Threads**
  10. * **std::async**
  11. * **std::packaged_task**
  12. * **std::promise and std::future**
  13. If the `promise` sets the value or the exception `more than once`, a `std::future_error` exception is thrown.
  14. If you destroy the `std::promise` without calling the set-method or a `std::packaged_task` before invoking it, a `std::future_error` exception with an error code `std::future_errc::broken_promise` would be stored in the shared state.
  15. If a future fut asks for the result `more than once`, a `std::future_error` exception is thrown.
  16. There is a `One-to-one` relationship between the promise and the future.
  17. * **std::shared_future**
  18. `One-to-many` relationship between a promise and many futures.
  19. * **Exceptions**
  20. If the callable used by `std::async` or by `std::packaged_task` throws an error, the exception is store in the shared state.
  21. When the future fut then calls `fut.get()`, the exception is rethrown, and the future has to handle it.
  22. * **Notifications**
  23. `Condition variables` to synchronise threads multiple times.
  24. A `promise` can send its notification only once.
  25. `promise` and `future` is the first choice
  26. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/condvstask.png)
  1. void waitForWork(std::future<void> && fut)
  2. {
  3. std::cout << "Worker: Waiting for work." << std::endl;
  4. fut.wait();
  5. std::cout << "work done\n";
  6. }
  7. void setReady(std::promise<void> &&pro)
  8. {
  9. std::cout << "Send data is ready.\n";
  10. pro.set_value();
  11. }
  12. void test()
  13. {
  14. using namespace std::chrono_literals;
  15. std::promise<void> pro;
  16. std::future<void> fut = pro.get_future();
  17. std::thread t1(waitForWork, std::move(fut));
  18. std::this_thread::sleep_for(2s);
  19. std::thread t2(setReady, std::move(pro));
  20. t1.join();
  21. t2.join();
  22. }
  1. ## II. Challenges
  2. ### 1. ABA Problem
  3. ABA means you read a value twice and each time it returns the same value A.
  4. Therefore you conclude that nothing changed in between.
  5. However, you missed the fact that the value was updated to B somewhere in between.
  6. ### 2. Blocking Issues
  7. It is a victim of a spurious wakeup or lost wakeup.
  8. ### 3. Breaking of Program Invariants
  9. ...
  10. ### 4. Data Race
  11. ...
  12. ### 5. Livelock
  13. The thread is wating a notification never fire or fired.
  14. ### 6. Deadlocks
  15. There are two main reasons for deadlocks:
  16. * **A mutex has not been unlocked.**
  17. * **You lock your mutexes in a different order.**
  18. #### 1. Locking a non-recursive mutex more than once
  19. #### 2. Lock Mutexes in Different Order
  20. ![](https://github.com/pvthuyet/Modern-Cplusplus/blob/master/resources/deadlock.png)

void deadlock(std::mutex& a, std::mutex& b) {
std::lock_guard g1(a);
std::lock_guard g2(b);
// do something here.
}
int main() {
std::mutex m1, m2;
std::thread t1(deadlock, std::ref(m1), std::ref(m2));
std::thread t2(deadlock, std::ref(m2), std::ref(m1));
return 0;
}

  1. #### 3. Solution
  2. * **Keep in mind only lock as soon as needed**
  3. * **Avoid necked Mutex**
  4. * **Avoid nested blocks:**
  5. Dont acquire a lock if you already hold one.
  6. * **Avoid calling user-supplied code while holding a lock**
  7. Because the code is user supplied, you have no idea what it could do; it could do anything, including acquiring a lock.
  8. * **Aquire locks in a fixed order**
  9. Using std::lock
  10. * **Use a lock hierarchy**
  11. * **Fix deadlock using std::lock and std::scoped_lock**
  12. Use std::unique_lock

void fixDeadlock(std::mutex& a, std::mutex& b) {
std::unique_lock g1(a, std::defer_lock);
std::unique_lock g1(b, std::defer_lock);
std::lock(g1,g2);
// do something here.
}

  1. or use std::lock_guard

void fixDeadlock(std::mutex& a, std::mutex& b) {
std::lock(a, b);
std::lock_guard g1(a, std::adopt_lock); // to make sure a will be released
std::lock_guard g1(b, std::adopt_lock); // to make sure b will be released
// do something here.
}

  1. or use std::scoped_lock

void fixDeadlock(std::mutex& a, std::mutex& b) {
std::scoped_lock scoLock(a, b);
// do something here.
}

  1. ### 7. False Sharing
  2. `False sharing` occurs if two threads read at the same time different variables a and b that are located on the same `cache line`.
  3. * `std::hardware_destructive_interference_size`: returns the minimum offset between two objects to avoid false sharing.
  4. * `std::hardware_constructive_interference_size`: returns the maximum size of contiguous memory to promote true sharing.

struct Sum{
alignas(std::hardware_destructive_interference_size) long long a{0};
alignas(std::hardware_destructive_interference_size) long long b{0};
};

  1. ### 8. Lifetime Issues of Variables
  2. ...
  3. ### 9. Moving Threads

std::thread t([]{std::cout << std::this_thread::get_id();});
std::thread t2([]{std::cout << std::this_thread::get_id();});
t = std::move(t2);// Issues: t must be call join() before move
t.join();
t2.join();
```

10. Race Conditions

….

C++11 Books:

ModernesCpp by Rainer Grimm
1
1

2

3