Modern C++ Tutorial Notes

written in front

Record the main points in "Modern C++ Tutorial".
Modern C++ refers to the grammatical features after C++11. Unless otherwise specified, the following grammatical features can only be used after C++11.

1. Enhancement of language usability

1. Constants

1.1 nullptr

  • effect:

    • Instead of assigning NULLa null pointer;
  • use:

char *a = nullptr;
  • reason:
    • Compilers usually define NULL = (void*)0either NULL = 0;
    • But since C++ does not allow void *implicit conversion to other types of pointers, it must be explicitly converted;
    • Therefore, when the function is passed parameters, the incoming function NULLdoes not know whether it is a call intor xxx *an overloaded function of the type;
    • nullptrUsed to distinguish between null pointers and 0, and deprecated NULL;
    • nullptroccupy the void*same space as
    • Notice:
      • NULLThe values ​​of and \0are both 0, so their inversion can be converted into trueBoolean values;
      • But nullptrthe value of is not 0, so its inversion cannot be converted to true, and it should be ptr == nullptrused to judge whether the pointer is empty;

1.2 constexpr

  • effect:

    • Let the user's explicitly declared function or object constructor become a constant expression at compile time ;
    • That is to say, the return value of the function can be used as a constant;
  • use:

    • constexprObject declared:
      • must be initialized with a constant expression ( constexpran expression consisting of a constant or);
    • constexprDeclared function:
      • If you want to pass in a parameter, it must be a constant expression;
      • The return value must be a constant expression;
      • Statements other than usingdirectives, typedefstatements, static_assertassertions, and returnstatements must not appear;
      • Starting with C++14 , local variables, loops and branches are available;
// 对象构造函数
constexpr int a = 1 + 2 + 3;

// 函数
constexpr int fibonacci(const int n) {
    
    
	return n==1 || n==2 ? 1 : fibonacci(n-1) + fibonacci(n-2);
}

int arr_1[a];
int arr_2[fibonacci(5)];

2. Variables and their initialization

2.1 Temporary variables can be declared in the conditional statement of if/switch

  • use:
    • After C++17, temporary variables can be declared in the conditional statement of if/switch;
if( 
	const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3);
    itr != vec.end()
) 
{
    
    
	*itr = 4;
}

[2.2] Initializer lists can be used in member functions

  • effect:

    • Allow custom classes class object = {}to be initialized in the form that can also be used;
  • use:

    • Used in formal parameters of member functions std::initializer_list<参数类型> list;
    • Can be used in constructors or in general member functions;
class MagicFoo {
    
    
	public:
	std::vector<int> vec;
	// 构造函数中使用
	MagicFoo(std::initializer_list<int> list) {
    
    
		for (std::initializer_list<int>::iterator it = list.begin();
			it != list.end(); ++it)
			vec.push_back(*it);
	}
	
	// 一般的成员函数中使用
	void foo(std::initializer_list<int> list) {
    
    
		for (std::initializer_list<int>::iterator it = list.begin();
			it != list.end(); ++it) 
			vec.push_back(*it);
	}
};

MagicFoo magicFoo = {
    
    1, 2, 3, 4, 5};
magicFoo.foo({
    
    6,7,8,9});

2.3 Can automatically bind to std::tuple type structure

  • effect:

    • Automatically unpack and bind the std::tuple type to several variables without knowing the type of each variable in the package;
  • use:

    • After C++17 , it can be automatically unpacked and assigned to multiple variables;
std::tuple<int, double, std::string> f() {
    
    
	return std::make_tuple(1, 2.3, "456");
}

// 自动将int、double和string的值绑定到x、y和z上
auto [x, y, z] = f();

[3]. Type deduction

3.1 auto

  • effect:

    • Automatically determine the type of the left value of the equality according to the right value of the equality;
  • use:

    • After C++20 , it can be used as a formal parameter of a function;
    • Array types cannot be deduced;
    • cannot be used as a template parameter argument;
    • The return value type can decltypebe deduced with collocation, and can be used without collocation after C++14 ;decltype
auto i = 5;					// i被推导为int
auto arr = new auto(10);	// arr被推导为int *
auto it = vec.begin();		// it被推导为容器对应的迭代器类型

template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
    
    
	return x + y;
}

// C++14之后
template<typename T, typename U>
auto add2(T x, U y){
    
    
	return x + y;
}

3.2 decltype

  • effect:

    • get the type of an expression;
  • use:

    • decltype(表达式)
    • Can be used decltype(auto)as the return type of a function, it can automatically deduce the return type of the forwarding function or the encapsulation function, that is, the case of calling other functions as the return type;
auto x = 1;
auto y = 2;
// 用x+y表达式的类型定义z
decltype(x+y) z;

// 比较x和int类型是否相同
if (std::is_same<decltype(x), int>::value)
	std::cout << "type x == int" << std::endl;

// 用decltype(auto)自动推导封装的返回类型
std::string look_up_a_string_1() {
    
    
	return lookup1();
}
decltype(auto) look_up_a_string_1() {
    
    
	return lookup1();
}

3.3 std::is_same

  • effect:

    • Determines whether two types are equal;
    • return boolean type;
  • use:

// 比较x和int类型是否相同
if (std::is_same<decltype(x), int>::value)
	std::cout << "type x == int" << std::endl;

4. Control flow

4.1 if constexpr

  • effect:

    • The Boolean value of the if condition is calculated during compilation;
    • This can speed up the speed of conditional judgment;
  • use:

// 注意泛型的实例化在编译过程中就已经实现了
template<typename T>
auto print_type_info(const T& t) {
    
    
	if constexpr (std::is_integral<T>::value) {
    
    
		return t + 1;
	} else {
    
    
		return t + 0.001;
	}
}
int main() {
    
    
	std::cout << print_type_info(5) << std::endl;
	std::cout << print_type_info(3.14) << std::endl;
}


/* 编译时的代码为:
int print_type_info(const int& t) {
	return t + 1;
}
double print_type_info(const double& t) {
	return t + 0.001;
}
int main() {
	std::cout << print_type_info(5) << std::endl;
	std::cout << print_type_info(3.14) << std::endl;
}
*/

[4.2] Interval for iteration

  • effect:

    • The class object that implements the iterator can be completely traversed without explicitly giving the start position and end position of the for loop;
  • use:

    • for(auto element: 实现了迭代器的对象), then in the loop you can elementread each element of the object with;
    • for(auto &element: 实现了迭代器的对象), and then each element of the object can be elementread and written in the loop ;
    • Generally used for basic containers other than adapters , because these containers implement iterators;
for (auto element : vec) {
    
    
	std::cout << element << std::endl; // read only
}
for (auto &element : vec) {
    
    
	element += 1; // read and write
}

5. Template

5.1 External Templates

  • effect:

    • Avoid automatic implicit instantiation by the compiler every time it encounters an instantiated object of the template, because this will cause the template to be instantiated repeatedly on the same type;
    • Explicit instantiation can explicitly specify where the template of the type is instantiated;
    • The reason why it is called an external template is that it is very similar to the use of external variables. Adding it externmeans that it is instantiated elsewhere, and it is used in this file but not repeatedly instantiated;
  • use:

    • (1) Instantiate in the compiled output file corresponding to this source code:
      • Class templates are template class 模板类名<实例化类型>instantiated explicitly with;
      • Function templates are template 函数返回值类型 模板函数名<实例化类型>(参数类型)instantiated explicitly with;
    • (2) Instantiate in the compiled output file corresponding to other source codes, and use in this file:
      • Class templates are extern template class 模板类名<实例化类型>instantiated explicitly with;
      • Function templates are extern template 函数返回值类型 模板函数名<实例化类型>(参数类型)instantiated explicitly with;
// 在本编译文件中实例化模板
template class std::vector<bool>; 
template int add<int>(int t1, int t2);

// 不在该当前编译文件中实例化模板
extern template class std::vector<double>; 
extern template int add<int>(int t1, int t2);

5.2 Nested template instantiation

  • effect:

    • Ability to instantiate templates with instantiated templates as types;
  • use:

std::vector<std::vector<int>> matrix;
  • reason:
    • In traditional C++ compilers, two consecutive right angle brackets >>are considered a right shift operator;
    • Therefore, it cannot be compiled successfully;

5.3 using defines template type aliases

  • effect:

    • In traditional usage, add the function of defining aliases for types and templates, which can be replaced typedef;
  • use:

    • (1) Use using namespace 命名空间名称the import namespace (traditional C++);
    • (2) Used using 基类::基类成员in subclasses to change the permissions of referenced base class members;
    • (3) Use using 别名 = 类型或者模板a designated alias;
// 命名空间
using namespace std;
using namespace std::vector;

// 在子类中改变基类成员的权限
class Base{
    
    
protected:
	int member;
};
class Derived: private Base {
    
      // 虽然是私有继承
public:
	using Base::member;  // 但用using后member成为了子类的public成员
}

// 指定普通类型别名
using ULL = unsigned long long;  //typedef unsigned long long ULL;
// 指定函数类型别名
using func = void(*)(int, int);  //typedef void(*func)(int, int);
// 指定类成员函数类型别名
using handler_t = void(ProcessPool::*)(int);
// 指定模板别名
template <typename T>
using mapInt = std::map<int, T>;
mapInt<bool> bmap;
  • reason:
    • typedefAliases cannot be defined for templates, because templates are not types, but are used to generate types;
    • Moreover, typedefthe writing method when defining the function pointer alias is very unique, and the form is not regular;
    • usingcan be completely typedefreplaced by

[5.4] Variadic templates

template <typename... TS>
void magic(Ts... args) {
    
    
	// 输出参数的个数
	std::cout << sizeof...(args) << std::endl;
}

// 1. 用递归实现模板参数的拆包,终止函数是一个参数的函数
//    这样传入的可变参数最少是1个
template<typename T0>
void printf1(T0 value) {
    
    
	// 仅一个参数
	std::cout << value << std::endl;
}
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
    
    
	// 函数重载,多个参数
	std::cout << value << std::endl;
	printf1(args...);
}
int main() {
    
    
	printf1(1, 2, "123", 1.1);
	return 0;
}

// 2. 用递归实现模板参数的拆包,终止函数是无模板且无参数的函数
//    这样传入的可变参数可以是0个
void printf1() {
    
    
	// 无参数
	return;
}
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
    
    
	// 函数重载,多个参数
	std::cout << value << std::endl;
	printf1(args...);
}
int main() {
    
    
	printf1(1, 2, "123", 1.1);
	return 0;
}

// 3. 用逗号表达式实现模板参数的拆包
//    但这种方式只适用于各个参数的处理方式相同的情况,使用的范围比较局限
template<typename T> 
void printf1(T value) {
    
    
	std::cout << value << std::endl;
}
template<typename... Ts>
void printf1(Ts... args) {
    
    
	// 使用std::initializer_list
	int arr[] = {
    
     (printf1(args), 0)... };
	// 等价于用下面的方式
	// std::initializer_list<int> {(printf1(args), 0)...};
}
int main() {
    
    
	printf1(1, 2, "123", 1.1);
	return 0;
}
 

// C++17后可以这样实现拆包
template<typename T0, typename... T>
void printf2(T0 t0, T... t) {
    
    
	// 一个或者多个参数
	std::cout << t0 << std::endl;
	if constexpr (sizeof...(t) > 0) printf2(t...);
}

5.5 Using literals as template parameters

  • effect:

    • In addition to using types as template parameters, you can also use literals as template parameters;
    • Literals can be regarded as constants defined without variable symbols, such as constants, strings, etc.;
  • use:

template <typename T, int BufSize>
class buffer_t {
    
    
public:
	T& alloc();
	void free(T& item);
private:
	T data[BufSize];
}
buffer_t<int, 100> buf; // 100 作为模板参数

5.6 Common Templates

  • (1) std::is_same

  • (2) std::decay

    • To degenerate types to primitive types, you can:
      • remove constand left and right value references,
      • degenerate function name or array name to function pointer;
    • Implementation principle reference: c++11 std::decay source code analysis ;
  • (3) std::enable_if

    • It is equivalent to the usage of if, which is used to check whether the type can be used;
    • Implementation principle reference: Several usages of std::enable_if ;
// 比较类型1是否和类型2相同,如果是,value=true,反之,value=false
std::is_same<类型1, 类型2>::value

// 获得类型的退化类型(基本类型)
std::decay<类型>::type

// 如果布尔值=true,则type=类型,否则编译出错(类型默认为void)
std::enable_if<布尔值, 类型>::type

6. Object-oriented

6.1 Delegating constructors

  • effect:

    • You can call another constructor in a constructor of the current class;
  • use:

    • The usage is similar to calling the parent class constructor;
class Base {
    
    
public:
	int value1;
	int value2;
	Base() {
    
    
		value1 = 1;
	}
	Base(int value) : Base() {
    
     // 委托Base() 构造函数
		value2 = value;
	}
};

6.2 Inheritance Construct

  • effect:

    • Directly inherit the constructor of the parent class in the subclass, so that there is no need to rewrite the constructor of the subclass;
  • use:

    • usingImplemented by keywords;
class Base {
    
    
public:
	int value1;
	int value2;
	Base() {
    
    
		value1 = 1;
	}
	Base(int value) : Base() {
    
     // 委托Base() 构造函数
		value2 = value;
	}
};
class Subclass : public Base {
    
    
public:
	using Base::Base; // 继承构造
};

6.3 Explicit virtual function overloading override and final

  • effect:

    • The function to avoid overloading is not a virtual function of the base class;
    • Avoid subclasses from overriding virtual functions overloaded by the current class;
  • use:

    • Use overrideto ensure that the current overload is the virtual function of the base class;
    • Use finalto ensure that the subclass will not overwrite the virtual function overloaded by the current class, or ensure that the subclass will not be derived again;
struct Base {
    
    
	virtual void foo(int);
};
struct SubClass: Base {
    
    
	virtual void foo(int) override; // 合法
	//virtual void foo(float) override; // 非法, 父类没有此虚函数
};

struct Base {
    
    
	virtual void foo() final;
};
struct SubClass1 final: Base {
    
    
}; // 合法
struct SubClass2 : SubClass1 {
    
    
}; // 非法, SubClass1 已final
struct SubClass3: Base {
    
    
	void foo(); // 非法, foo 已final
};

6.4 Explicitly using or disabling default functions

  • effect:

    • Explicitly use or disable default functions;
  • use:

    • 函数定义 = defaultthen explicitly use the default function;
    • 函数定义 = deletethen explicitly disable the default function;
class Magic {
    
    
public:
	Magic() = default; // 显式声明使用编译器生成的构造
	Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
	Magic(int magic_number);
}
  • reason:
    • C++ generates default constructors, copy constructors, overloaded assignment operators, destructors, new operators, and delete operators by default;
    • But sometimes it is necessary to explicitly use or prohibit the use of these default generated functions instead of handing over control to the compiler;

6.5 Strongly Typed Enums

  • effect:

    • Let enumeration types be more than just inttypes;
  • use:

    • By enum class 枚举类名: 类型 {};defining an enumeration class, you can specify the type of the enumeration class;
    • For the use of traditional enumeration types, please refer to: C++ enumeration type detailed explanation ;
// 传统C++枚举类型
enum color_set {
    
    red, blue, green};

color_set color1;
color1 = red; 
color_set color2 = color1;
int i = color1;  // 相当于int i = 0;
//color1 = 1;  // 不允许将int赋值给enum
cout << color1;  // 相当于cout << int(0);
//cin >> color1;  // 不允许输入

// 强类型枚举
enum class color_set1: unsigned int {
    
    red, blue, green};
enum class color_set2: int {
    
    red, blue, green};

color_set1 color1 = red;
color_set2 color2 = red;
//color1 == color2  // 非法
//int i = color1;  // 非法
//color1 == 0  // 非法
  • reason:
    • Traditional C++ enumerations are actually inttypes;
    • But intdifferent from the enumeration variable, the value of the enumeration variable is limited, determined by the number of identifiers when it is defined;
    • Because the value comparison of different enumeration types is actually converted into an integer comparison, it is not type safe ;

2. Strengthening of the language runtime

[1]. Lambda expression

  • effect:

    • Provides the functionality of anonymous functions;
    • That is, a function is defined, but it does not need to be named as an external function, it can be regarded as a function object;
    • Returns a function object rather than a function pointer;
  • working principle:

    • Generate an anonymous object of an anonymous class;
    • Then overload operator()the operator;
  • use:

    • Expressions are [捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 { // 函数体 }, including:
      • Capture list: capture variables outside the function into the function, which is equivalent to passing parameters;
        • Value capture : [外部变量名], copy a copy of an external variable into the function;
        • Reference capture : [&外部变量名], pass the reference of the external variable into the function;
        • Implicit capture : [&]alternatively , let the compiler automatically deduce[=] the required capture by reference or by value ;
        • After C++14, it is allowed to capture rvalues, that is, expression values, function return values, etc.;
      • Parameter list: the parameter variables that the caller needs to pass in;
      • mutable: The incoming value capture variable can be modified inside the function, but has no effect on the value outside the function;
      • return type: the return value type of the function;
    • scenes to be used:
      • Pass the algorithm function in STL as a function pointer parameter;
      • As a function that implements a simple function;
    • You can refer to the blog: "In-depth understanding of C++11" notes - lambda function ;
// 和STL算法库中的函数搭配使用
sort(testdata.begin(), testdata.end(), [](int a, int b){
    
     
	return a > b; });
for_each(a, a+4, [=](int x) {
    
     
	cout << x << " "; });
auto pos = find_if(coll.cbegin(), coll.cend(), [=](int i) {
    
                     
    return i > x && i < y; });
vec_data.erase(std::remove_if(vec.date.begin(), vec_data.end(), [](int i) {
    
     
    return n < x;}), vec_data.end());

// C++14后,可以在参数列表中使用auto
auto add = [](auto x, auto y) {
    
    
	return x + y;
};
cout << add(1, 4) << endl;

[2]. Function object wrapper

2.1 std::function

  • effect:

    • Equivalent to a function container, functions and function pointers can be treated as objects;
    • is type safe;
  • use:

    • #include <functional>
    • std::function<函数返回类型(函数参数类型)> 容器名 = 函数名或者lambda表达式
#include <functional>

int foo(int para) {
    
    
	return para;
}
// 封装函数foo
std::function<int(int)> func = foo;

int important = 10;
// 封装lambda表达式
std::function<int(int)> func2 = [&](int value) -> int {
    
    
	return 1+value+important;
};

std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;

2.2 std::bind

  • effect:

    • Used to bind fixed parameters to existing functions to generate new functions with fewer parameters;
    • It is equivalent to modifying the parameter list of an existing function;
  • use:

    • #include <functional>
    • The parameter passing of the function generated by std::bind is value passing by default;
    • You can refer to the blog post: std::bind of c++11 is simple to use ;
#include <functional>

// 1. 绑定普通函数
void print(int a, int b, int c)
{
    
    
	std::cout << "a = " << a << ", b=" << b << ", c=" << c << "\n\n";
}
...
// 将占位符2绑定到a,将2绑定到b,将占位符1绑定到c,生成新函数func(_1, _2)
auto func1 = std::bind(print, std::placeholders::_2, 2, std::placeholders::_1);
func1(3, 4);  // 相当于是print(4, 2, 3)
...

// 2. 绑定类成员函数
class A {
    
    
public:
    void print(int x, int y) {
    
    
        std::cout << "x + y = " << x + y << std::endl;
    }
};
...
A a;
// 将a对象的成员函数绑定到f2上
auto func2 = std::bind(&A::print, &a, 1, std::placeholders::_1);
func2(2);  // 相当于是a.print(1,2)
...

[3]. Rvalue reference

3.1 The concept of left and right values

  • Lvalue : the value on the left of the equal sign, the persistent object that still exists after the equation, and the address that can take the value;
  • Rvalue : the value to the right of the equal sign, a temporary object that no longer exists after the equality;
    • A prvalue (no name, cannot take address):
      • Temporary variables returned by non-reference;
      • Temporary variables generated by operation expressions;
      • literals other than strings (string literals are lvalues);
      • Lambda expression;
    • Xvalue (has a name and can take an address): a temporary variable that is about to be destroyed but is referenced by an rvalue to keep it alive;
      • An lvalue is an rvalue of an rvalue reference ;
      • An lvalue is an rvalue referenced by a constant lvalue ;
  • You can refer to the blog: C++ Scenery in the Fog 10: Talk about lvalues, prvalues ​​and dying values ;

3.2 left and right value references

  • lvalue reference: T &;
std::string & str
  • rvalue reference: T &&;
    • The object used as a reference (equivalent to taking an address) is a dying value ;
    • If the referenced object is not an xvalue, you need to std::move(左值变量)convert the lvalue to an rvalue. Note that this will make the original lvalue variable inaccessible;
    • An rvalue reference variable is itself an lvalue ;
std::string && str
  • Some usage examples:
std::string lv1 = "string,"; // lv1 是一个左值
// std::string&& r1 = lv1; // 非法, 右值引用不能引用左值
std::string&& rv1 = std::move(lv1); // 合法, std::move 可以将左值转移为右值
std::cout << rv1 << std::endl; // string,

const std::string& lv2 = lv1 + lv1; // 合法, 常量左值引用能够延长临时变量的生命周期
// lv2 += "Test"; // 非法, 常量引用无法被修改
std::cout << lv2 << std::endl; // string,string,

std::string&& rv2 = lv1 + lv2; // 合法, 右值引用延长临时对象生命周期
rv2 += "Test"; // 合法, 非常量引用能够修改临时变量
std::cout << rv2 << std::endl; // string,string,string,Test

3.3 Move Semantics

  • Copy semantics :

    • Completely copy the resources of one object to another object, that is, deep copy ;
  • Move Semantics :

    • Move the resources of one object to another object without copying the data ;
    • Only the resources on the heapnew the space requested:
      • Because the resources applied on the heap are generally relatively large, the cost of using copy semantics is very high;
      • Using move semantics can avoid frequent new and delete, thereby avoiding copy overhead;
      • nullptrImplementation method: complete resource movement by copying new pointers and setting old pointers ;
    • Resources on the stack cannot be moved , that is, non new-space, including pointers and POD types, etc.;
      • Because the resources applied on the stack can only be destroyed when the scope ends, and cannot be released and transferred manually, so the real move semantics cannot be realized;
      • However, these resources can also be set to default initial values ​​to achieve pseudo-move semantics (that is, unavailable after moving, but not truly inaccessible);
  • Related functions involved:

    • std::move()

      • Function: convert an lvalue into a xvalue, and obtain an rvalue reference of the lvalue ;
        • But in fact, the original lvalue is not released, but a reference to its rvalue is added;
        • By using this rvalue reference as the actual parameter of the function, you can call overloaded functions that implement move semantics , such as move constructors and move assignment operator overloaded functions;
        • is the only way to convert an lvalue to an rvalue;
      • reference:
    • Move constructor A(A&& _a):

      • Role: implement the constructor of mobile semantics ;
        • The formal parameter is an rvalue reference;
        • Move the resource on the heap of the formal parameter to the current object, that is, make the pointer of the current object point to the resource on the heap of the formal parameter, and then make the pointer of the formal parameter empty;
        • Note: There is a default move constructor, but it does not implement move semantics, but only implements shallow copy copy semantics like the default constructor;
    • Move assignment operator overloaded functions A& operator=(A&& _a):

      • Function: the assignment operator overload function that realizes the move semantics ;
        • Move the resource on the heap of the formal parameter to the current object, that is, make the pointer of the current object point to the resource on the heap of the formal parameter, and then make the pointer of the formal parameter empty;
        • Note: There is a default move assignment operator overload function, but it does not implement move semantics, but only implements shallow copy copy semantics like the default assignment operator overload function;
  • Implement move semantics:

    • You must manually implement the class's move constructor and move assignment operator overloading functions;
    • Then, std::move()move semantics are realized by calling the move constructor or the move assignment operator overloaded function;
    • The calling methods include:
      • (1) Explicitly call the move constructor or move assignment operator overloaded function ;
      • (2) Pass an rvalue argument into a value-passing formal parameter of a class object ;
        • At this time, the move constructor is implicitly called , which is move semantics;
        • If an lvalue is passed, the copy constructor is called, which is copy semantics;
        • If the move constructor is not implemented, the copy constructor is called automatically, because const A &constant lvalue references can also be followed by rvalue references;
        • If it is passed to an rvalue reference formal parameter of a class object, it is neither move semantics nor copy semantics, because at this time the resource of the actual parameter has not been moved or copied, only the access method within the function has been added ( It is equivalent to a pointer pointing), and its function is the same as that of an lvalue reference in use (the difference is that it is equivalent to an overload of an lvalue reference, which can distinguish overloaded functions);
      • (3) assign a function's temporary return object (rvalue) to a class object ;
        • At this time, the overloaded function of the move assignment operator is implicitly called , which is move semantics;
        • If the move assignment operator overload function is not implemented, the assignment operator overload function is called automatically, because the const A &constant lvalue reference can also be connected to the rvalue reference;
        • If the object returned by the function is assigned an rvalue reference , it is neither move semantics nor copy semantics, because at this time the resources of the temporary object have not been moved and copied, and only the way of access outside the function (equivalent to pointer pointing) has been added. , the temporary object is the dying value at this time;
  • A code example is as follows:

	class A{
    
    
	public:
		char* ptr;
		int val;
		std::string s;

		A() :ptr("hello"), val(5), s("world") {
    
    };
		A(const A& _a) {
    
    
			// 拷贝语义
			printf("copy constructor\n");
			int _cnt = 0;
			while (*(_a.ptr + _cnt) != '\0') {
    
    
				_cnt++;
			}
			ptr = new char[_cnt + 1];
			memcpy(ptr, _a.ptr, sizeof(char) * (_cnt + 1));
			val = _a.val;
			s = _a.s;
		}
		A(A&& _a) {
    
    
			// 移动语义
			printf("move constructor\n");
			val = std::move(_a.val);
			ptr = std::move(_a.ptr);
			s = std::move(_a.s);
			_a.val = 0;
			_a.ptr = nullptr;
		}
		A& operator=(const A& _a) {
    
    
			// 拷贝语义
			printf("copy operator =\n");
			int _cnt = 0;
			while (*(_a.ptr + _cnt) != '\0') {
    
    
				_cnt++;
			}
			ptr = new char[_cnt + 1];
			memcpy(ptr, _a.ptr, sizeof(char) * (_cnt + 1));
			val = _a.val;
			s = _a.s;
		}
		A& operator=(A&& _a) {
    
    
			// 移动语义
			printf("move operator =\n");
			val = std::move(_a.val);
			ptr = std::move(_a.ptr);
			s = std::move(_a.s);
			_a.val = 0;
			_a.ptr = nullptr;
		}
	};

	A a;
	printf("c: %s %s\n", a.ptr, a.s.c_str());
	printf("a: %d\n", &a.ptr);

	// 拷贝语义
	A b(a);
	printf("c: %s %s\n", b.ptr, b.s.c_str());
	printf("b: %d\n", &b.ptr);

	// 移动语义
	A c(std::move(a));
	printf("c: %s %s\n", c.ptr, c.s.c_str());	
	printf("c: %d\n", &c.ptr);

	auto func = []() {
    
    
		A tmp;
		tmp.ptr = "hello again";
		tmp.val = 10;
		tmp.s = "new world";
		return tmp;
	};
	A d;
	// 移动语义
	d = func();
	printf("d: %s\n", d.ptr);
	printf("d: %s\n", d.s.c_str());

	A e;
	// 移动语义
	e = std::move(d);
	printf("e: %s\n", e.ptr);
	printf("e: %s\n", e.s.c_str());

3.4 Citation collapse rules

  • Used to pass reference parameters again ;
  • The reference collapse rules (also called reference collapsing rules) are as follows:

Quote Collapse Rules

  • When only when the actual parameter is a right reference type (the x value) and the formal parameter is a right reference type , the type passed into the function is a right reference type, and the rest are left reference types, that is to say, the form of the formal parameter cannot Determine the type of reference ;
  • Note that a right reference type variable itself is an lvalue, so if it continues to pass down, it should be regarded as a left reference type ;
  • Reference collapse rules only apply when there is automatic type deduction , as follows:
// 模板函数
template<typename T>
void f(T&& param);

// auto自动推导
auto&& var2 = var1;

// typedef自动推导
template<typename T>
typedef T&& RRef;

// using自动推导
template<typename T>
using RRef = T&&;

// decltype自动推导
decltype(w1)&& v2 = w2;    
  • If the type is not automatically deduced, the left and right value references are actually two different types, namely:
    • cannot pass an lvalue reference argument to an rvalue reference parameter ;
    • But you can pass an rvalue reference argument to a constant lvalue reference parameter ;

3.5 Perfect forwarding

  • effect:

    • Avoid passing the right reference type as an lvalue when it is passed down as a reference ;
    • In this way, the somewhat convoluted setting that the right reference type variable itself is an lvalue can be ignored;
  • use:

    • Perfect forwarding can be done using std::forward<右值或左值引用类型>(右值或左值引用变量);
void pass(T&& v) {
    
    
	// 总作为左值转发
	std::cout << " 普通传参: ";
	reference(v);
	// 强制转为右值转发
	std::cout << " std::move 传参: ";
	reference(std::move(v));
	// 使用std::forward转发
	std::cout << " std::forward 传参: ";
	reference(std::forward<T>(v));
	// static_cast<T&&>转换也符合引用坍缩规则
	std::cout << "static_cast<T&&> 传参: ";
	reference(static_cast<T&&>(v));
}

3. Container

1. std::array

  • effect:
    • as a replacement for traditional arrays;
    • Suitable for array objects with fixed object size;

2. std:: forward_list

  • effect:
    • As an alternative to the traditional one-way linked list;

3. std::unordered_map

  • effect:
    • The bottom layer uses a hash table;
    • The time complexity is smaller than std::map, which is constant time complexity;
    • Orderly traversal cannot be performed automatically;

4. std::unordered_set

  • effect:
    • The bottom layer uses a hash table;
    • The time complexity is smaller than std::set, which is constant time complexity;
    • Orderly traversal cannot be performed automatically;

5. std::tuple

  • effect:

    • Break through the limitation that std::pair can only store two elements, and std::tuple can store any number of elements;
  • use:

    • #include <tuple>
    • std::make_tuple(参数1,参数2...)Used to return a std::tuple type tuple composed of parameters;
    • std::get<元组下标>(元组变量)It is used to obtain the element of the subscript corresponding to the tuple variable, which can be read and written;
    • std::tie(变量名1, 变量名2...) = 元组变量It is used to unpack the tuple variable and then assign it to the corresponding variable name, which can std::ignorebe used as a placeholder for the variable name;
    • std::tuple_cat(元组变量1, 元组变量2)for merging two tuples;
    • std::tuple_len(元组变量)Used to return the number of tuple elements (tuple length);
    • After C++14, it can std::get<元素类型>(元组变量)be used to obtain this type of element in the tuple, but if the element of this type is not unique, there will be a compilation error;
    • After C++17, you can use to std::tuple_index(元组变量, 元组下标)obtain the element corresponding to the subscript of the tuple;
    • reference:
auto student = std::make_tuple(3.8, ’A’, " 张三");

std::get<0>(student) = 3.6;  // 修改元组的元素
cout << std::get<0>(student) << endl;  // 读取元组的元素
std::get<double>(student) = 3.6  // C++14后

std::tie(gpa, std::ignore, name) = student; 

auto new_tuple = std::tuple_cat(get_student(1), std::move(t));

for(int i = 0; i != tuple_len(new_tuple); ++i)
	// 运行期索引,C++17后
	std::cout << tuple_index(new_tuple, i) << std::endl;

4. Smart pointers and memory management

[1]. std::shared_ptr

  • effect:

    • Record how many shared_ptr points to the same object;
    • When the reference count is 0, the object is automatically deleted;
  • use:

    • #include <memory>
    • Use std::make_shared<对象类型>(对象值)can generate an object and return its shared_ptr pointer, which is recommended;
    • Use get()can obtain raw pointers without increasing the reference count;
    • Use reset()to clear the pointer and reference count of the current shared_ptr, and decrease the reference count of other shared_ptr pointing to the same object by one;
    • can be assigned as nullptr;
std::make_shared<int> pointer0(new int);  // 不推荐这样使用
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1

int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3

pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = "
<< pointer2.use_count() << std::endl; // pointer2 已reset; 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2

pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // pointer3 已reset; 0

[2]. std::unique_ptr

  • effect:

    • Exclusively point to an object, and prohibit other smart pointers from sharing an object with it;
  • use:

    • #include <memory>
    • std::unique_ptr<对象类型> 智能指针名(指向对象的指针)An object can be generated with;
    • After C++14, you can use it to std::make_unique<对象类型>(对象值)generate an object and return its unique_ptr pointer, which is recommended;
    • Although std::unique_ptr is exclusive, it can be used std::move(unique_ptr)to transfer the object it points to to another std::unique_ptr;
    • can be assigned as nullptr;
std::unique_ptr<Foo> p1(std::make_unique<Foo>());
// p1 不空, 输出
if (p1) p1->foo();
{
    
    
	std::unique_ptr<Foo> p2(std::move(p1));
	// p2 不空, 输出
	f(*p2);
	// p2 不空, 输出
	if(p2) p2->foo();
	// p1 为空, 无输出
	if(p1) p1->foo();
	p1 = std::move(p2);
	// p2 为空, 无输出
	if(p2) p2->foo();
	std::cout << "p2 被销毁" << std::endl;
}
// p1 不空, 输出
if (p1) p1->foo();
// Foo 的实例会在离开作用域时被销毁

[3]. std::weak_ptr

  • effect:

    • Weak references will not increase the reference count;
    • cannot take values ​​(no * operator) and take pointers (no -> operator);
    • It is only used to check whether the object pointed to by shared_ptr still exists, or to obtain a new shared_ptr pointing to the object;
  • use:

    • #include <memory>
    • Weak_ptr can only be built through shared_ptr;
    • You expired()can check whether the object currently pointed to still exists, and return a Boolean type;
    • Use use_count()to check the reference count of the object currently pointed to;
    • Use lock()a shared_ptr pointer that can return the current pointing object;
    • You can refer to the blog: basic usage of weak_ptr and how to solve circular references ;
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);

auto new_sp = wp.lock();
if(wp.expired()) {
    
    
	cout << "weak_ptr无效,资源已释放";
}
else {
    
    
	cout << "weak_ptr有效, *new_sp = " << *new_sp << endl;
}
  • reason:
    • If there is a shared_ptr inside the object pointed to by shared_ptr, it may cause the reference count of the current shared_ptr to not be 1, resulting in a deadlock of release (circular reference problem);
    • Because if you want to release the memory pointed to by shared_ptr, you need its reference count to be 0;
    • But if the reference count of shared_ptr is 0, it may need to release the memory space it points to first, because there is a smart pointer inside that may occupy the reference count of the current object;
    • Therefore, the memory space cannot be released and causes a leak, as shown in the following figure:

memory leak

4. Summary

  • The usage of the three smart pointers is summarized as follows:
    • weak_ptr
      • can only be initialized by shared_ptror weak_ptr;
      • can only be assigned by shared_ptror weak_ptr;
    • unique_ptr
      • Can only be initialized by object pointer or std::move(unique_ptr);
      • can only be assigned by nullptr, std::move(unique_ptr)or unique_ptr<对象类型>(对象指针);
    • shared_ptr
      • Can be initialized by object pointer, shared_ptr, std::move(unique_ptr)or weak_ptr;
      • Can be assigned by nullptr, shared_ptr, std::move(unique_ptr)or shared_ptr<对象类型>(对象指针);

Usage summary

5. Parallelism and concurrency

  • Here mainly introduces multi-threaded programming;

[1]. std::thread

  • effect:

    • Create a thread instance of execution;
  • use:

    • #include <thread>
    • Create std::thread 线程实例名(线程执行的函数名, 函数参数1, 函数参数2...)a thread to execute the function with;
      • The function name executed by the thread must be a global function or a static member function ;
      • Note that the function parameters here are std::bind()the same as, if it is passed by value or by pointer, it is the same as a normal function, but if it is passed by reference, it needs to be used std::ref(对象名)instead of directly used 对象名as an actual parameter;
        • If not used std::ref(对象名), ordinary lvalue reference passing is still the implementation of value passing, and rvalue reference passing (use std::move()) is the real implementation of reference passing;
        • This is mainly to remind users to pay attention to the situation that the life cycle of the referenced object may expire when it is used inside the function , but in fact, the memory pointed to by the passed pointer is also at risk of being released;
      • Reference blog:
    • Threads can be created with Lambda expressions as function parameters;
    • Member functions of a thread instance:
      • join()Used to block the thread that creates the thread instance until the execution of the thread instance is completed;
        • If the thread is not detached, and other threads are still alive when the main thread destroys other threads, terminate called without an active exceptionan error will occur;
        • Therefore, when not leaving the thread, it must be used join()to make the main thread wait for each thread to end before ending;
      • detach()It is used to detach the thread instance from the thread that created the thread instance and become a daemon thread, so:
        • Control of the thread instance can no longer be obtained through the thread instance name;
        • However, it is still possible std::futureto obtain the execution result of the thread instance in the thread that created the thread instance;
        • After the respective executions are completed, the system releases their resources without affecting each other, which can prevent the unfinished sub-threads from being terminated after the thread of the main process ends;
#include <iostream>
#include <thread>
int main() {
    
    
	std::thread t([](){
    
    
		std::cout << "hello world." << std::endl;
	});
	// 阻塞main()直至t线程执行完毕
	t.join();
	return 0;
}

[2]. std::mutex

  • effect:

    • Provide a mutex for the thread;
    • Some concepts:
      • Critical section : A code area that only allows one thread to access, but only one process can achieve mutual exclusion access;
      • Mutex : kernel object, which can perform lock operation in the kernel, and can achieve mutual exclusion access across multiple processes;
      • Four conditions for a deadlock to occur:
        • Mutual exclusion: the requested resources are mutually exclusive;
        • Request and hold: When the request is blocked, the existing resources continue to be kept and not released;
        • No deprivation: the resources occupied by other threads cannot be deprived, and can only be released by itself;
        • Loop waiting: A loop of resource waiting needs to appear;
    • The implementation overhead of mutex locks is very high, which is suitable for mutual exclusion of larger critical section codes;
  • use:

    • #include <mutex>
    • Member function:
      • lock(): The current thread tries to lock the mutex,
        • If successful, the current thread owns the mutex until unlock();
        • If unsuccessful, wait for other threads to release the mutex lock until successful, and the current thread is blocked;
      • unlock(): The current thread releases the mutex;
      • try_lock(): try to lock the mutex,
        • If successful, the current thread owns the mutex until unlock();
        • If unsuccessful, return false and the current thread will not be blocked;
    • std::lock_guard<互斥量类型> 名称(互斥量变量): RAII syntax for mutexes, for:
      • Instead mutex对象.lock(), try to lock the mutex object when the object is constructed ;
      • Automatically release the mutex at the end of the scope (including abnormal end conditions such as abnormal exit), without manually calling unlock()the release;
      • Notice:
        • release()It must be done before manual operation unlock(), otherwise the mutex cannot release the lock, which will cause other places to fall into a competitive lock deadlock;
        • So try not to use it release(), especially not to misuse it release();
    • std::unique_lock<互斥量类型> 名称(互斥量变量): RAII syntax of mutex, more flexible than lock_guard, allowing:
      • Manually release the mutex within the critical section;
      • Automatically release the mutex at the end of the critical section;
      • It is even possible to transfer the lock of the mutex to the unique_lock object outside the scope in the form of a return value to extend the holding time of the lock;
      • Recommended over lock_guard and native semaphores;
    • You can refer to the blog:
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

// 调用mutex成员函数
volatile int counter(0); // non-atomic counter
std::mutex mtx;           // locks access to counter
void attempt_10k_increases() {
    
    
    for (int i=0; i<10000; ++i) {
    
    
        if (mtx.try_lock()) {
    
       // only increase if currently not locked:
            ++counter;
            mtx.unlock();
        }
    }
}

// 使用lock_guard
void critical_section(int change_v) {
    
    
	static std::mutex mtx;
	std::lock_guard<std::mutex> lock(mtx);  // 相当于mtx.lock()
	// 执行竞争操作
	v = change_v;
	// 离开此作用域后mtx 会被释放
}

// 使用unique_lock
void critical_section(int change_v) {
    
    
	static std::mutex mtx;
	std::unique_lock<std::mutex> lock(mtx);  // 相当于mtx.lock()
	// 执行竞争操作
	v = change_v;
	std::cout << v << std::endl;
	// 将锁进行释放
	lock.unlock();  // 相当于mtx.unlock()
	// 在此期间,任何人都可以抢夺v 的持有权
	// 开始另一组竞争操作,再次加锁
	lock.lock();  // 相当于mtx.lock()
	v += 1;
	std::cout << v << std::endl;
}

[3]. std::packaged_task和std::future

  • effect:

    • std::packaged_task and std::future are usually used together;
      • std::packaged_task : It is equivalent to encapsulating a function so that it can be called in a multi-threaded environment, and the execution result of the encapsulating function can be obtained through std::future;
      • std::future : blocks the current process until the asynchronous result of the std::packaged_task function executed by other threads is obtained;
    • std::packaged_task is similar to std::function, but provides function encapsulation and result return in a multi-threaded environment;
    • std::future is equivalent to a synchronization method for asynchronously calling threads, namely barrier;
    • Some concepts:
      • Synchronization: After issuing a call, you need to wait for it to complete before continuing to execute, such as calling future.get()or thread.join();
      • Asynchronous: After sending a call, it can continue to execute without waiting for it to complete, such as creating an threadexecution function;
  • use:

#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
    
    
    for (int i=from; i!=to; --i) {
    
    
        std::cout << i << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Finished!\n";
    return from - to;
}

int main ()
{
    
    
    std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
    std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.

    std::thread th(std::move(task), 10, 0);   //创建一个新线程完成计数任务.

    int value = ret.get();                    // 等待任务完成并获取结果.

    std::cout << "The countdown lasted for " << value << " seconds.\n";

    th.join();
    return 0;
}

[4]. std::condition_variable

  • effect:

    • Provide a semaphore mechanism for a group of threads competing for the same mutex, allowing to block the current thread or wake up other blocked threads;
    • That is, on the basis of mutex locks, a synchronization mechanism is added to allow control of the sequence of competing mutex locks , avoiding meaningless competition and consuming resources;
  • use:

    • #include <condition_variable>
    • std::condition_variable 条件变量名;Define condition variables;
    • In a group of threads contending for the same mutex, call:
      • 条件变量对象.wait(unique_lock对象), the current thread is blocked, and at the same time release the owned mutex object lock;
      • 条件变量对象.wait(unique_lock对象, bool类型返回值函数), only when the function returns a value false, the current thread will be blocked and the owned mutex object lock will be released at the same time;
      • 条件变量对象.notify_all(), release the owned mutex object lock, wake up all wait()threads, and let them compete for the mutex semaphore;
      • 条件变量对象.notify_one(), release the owned mutex object lock, wake up a certain wait()thread, and let them compete for the mutex semaphore, but there is no way to achieve concurrent competition in this way, the efficiency is low, and it is not recommended to be used in a concurrent environment;
    • The object of attention wait()is the object held mutex, unique_locknot mutexthe object itself;
    • Reference blog:
#include <queue>
#include <chrono>
#include <mutex>
#include <thread>
#include <iostream>
#include <condition_variable>
int main() {
    
    
	std::queue<int> produced_nums;
	std::mutex mtx;
	std::condition_variable cv;
	bool notified = false; // 通知信号
	// 生产者
	auto producer = [&]() {
    
    
		for (int i = 0; ; i++) {
    
    
			std::this_thread::sleep_for(std::chrono::milliseconds(900));
			std::unique_lock<std::mutex> lock(mtx);
			std::cout << "producing " << i << std::endl;
			produced_nums.push(i);
			notified = true;
			// 释放mtx,唤醒所有wait(mtx)的线程
			cv.notify_all();
		}
	};
	// 消费者
	auto consumer = [&]() {
    
    
		while (true) {
    
    
			std::unique_lock<std::mutex> lock(mtx);
			while (!notified) {
    
     // 避免虚假唤醒
				// 释放mtx,等待别的线程唤醒自己
				cv.wait(lock);
				// 虚假唤醒:可能是由于别的原因而非notify()让自己获得了互斥量锁
			}
			// 消费者慢于生产者,则短暂取消锁,使得生产者有机会在消费者消费前继续生产
			lock.unlock();
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));
			// 加锁消费
			lock.lock();			
			while (!produced_nums.empty()) {
    
    
				std::cout << "consuming " << produced_nums.front() << std::endl;
				produced_nums.pop();
			}
			notified = false;
		}
	};
	// 分别在不同的线程中运行
	std::thread p(producer);
	std::thread cs[2];
	for (int i = 0; i < 2; ++i) {
    
    
		cs[i] = std::thread(consumer);
	}
	p.join();
	for (int i = 0; i < 2; ++i) {
    
    
		cs[i].join();
	}
	return 0;
}

[5]. std::atomic

  • effect:

    • Provide CPU instruction-level atomic operations for threads to achieve mutually exclusive variable operations;
    • The overhead is less than std::mutex, which is used for mutual exclusion of variables;
  • use:

    • #include <atomic>
    • Not all types can implement atomic operations, but integer and floating-point types can, and other types can use std::atomic<T>::is_lock_free()functions to check whether the corresponding atomic type of T type supports real atomic operations;
    • Member function:
      • fetch_add(): Addition operation, also has "+" operator overload;
      • fetch_sub(): Subtraction operation, also has "-" operator overload;
      • load(): return atomic variable value;
      • store(): update atomic variable value;
      • exchange(): Set an atomic variable to a new value, and return the old value;
    • Regarding atomicthe atomicity of variable overloaded functions and member functions:
      • Not all assignment operations are atomic;
      • In general, the member functions that call it are basically atomic, but operator overloading may make multiple atomic operations invisible;
      • The self-increment and self-decrement operations are atomic, but special attention is that x = x + ythis operation is not atomic;
      • The recommendation is to use its member functions as much as possible instead of overloading its operators, so that the atomicity of the operation is more intuitive;
    • Reference blog:
#include <atomic>
#include <thread>
#include <iostream>
//std::atomic<int> count = {0};
int main() {
    
    
	std::atomic<int> count;
	std::atomic_init(&count, 0);
	std::thread t1([](){
    
    
		count.fetch_add(1);
	});
	std::thread t2([](){
    
    
		count++; // 等价于fetch_add
		count += 1; // 等价于fetch_add
	});
	t1.join();
	t2.join();
	std::cout << count << std::endl;
	return 0;
}

[6]. Memory model of atomic operation

6.1 Consistency Model

  • (1) Linear consistency/strong consistency/atomic consistency:
    • Every time you can read the latest data written by the variable;
    • The order of operations seen by all threads is consistent with the order under the global clock;
    • The global clock order is the time order in which events actually occur, but due to communication delays, the order of operations seen by each thread is not necessarily the global clock order;
    • As shown in the figure below, the order seen by each thread must be write x = 1, write x = 2, read x = 2;

linear consistency

  • (2) Sequential consistency:
    • Every time you can read the latest data written by the variable;
    • However, it is not required that the order of operations seen by all threads is consistent with the order under the global clock. It is only required to be able to find a reasonable order of global operations in the eyes of all threads , which conforms to the read and write order of the program;
    • Zookeeper uses sequential consistency;
    • You can refer to the blog: What is sequential consistency? ;
    • As shown in the figure below, each thread is guaranteed to be able to read x = 3, but it is not guaranteed that the seen xwrite 1 must be executed before writing 2;

sequential consistency

  • (3) Causal consistency:
    • It is only guaranteed that the order of causal operations seen by each thread is in line with the causal order;
    • The sequence of operations without causality is not guaranteed;
    • WeChat Moments uses causal consistency;
    • As shown in the figure below, it is only guaranteed that each thread executes after cseeing aand writing;b

causation

  • eventual consistency:
    • If there is no write operation, the results read by all threads are consistent in the end;
    • It does not guarantee that the currently read results must be the latest, only that the final read results must be the latest;
    • As shown in the figure below, it is only guaranteed that each thread will be able to read it eventually x = 4, but not necessarily at present;

eventual consistency

6.2 std::memory_order

  • It is possible to control the consistency of atomic operations for atomic objects load()and increase parameters , thereby reducing the store()overhead of synchronization;fetch_add()std::memory_order_xxx
  • (1) Sequential consistency model:
    • std::memory_order_seq_cst
    • The atomic operations of each thread satisfy sequential consistency;
    • like:
counter.fetch_add(1, std::memory_order_seq_cst);
  • (2) release/acquire model:

    • std::memory_order_release
      • for store();
      • Ensure that the read and write operations of all variables by the code before the current thread will not occur after the release operation;
      • Equivalent to a barrier for write operations;
      • After the release of this thread ends, all writes are visible to other threads that impose acquire semantics, because release will synchronize all write operations of this thread to memory ;
      • Equivalent to the unlock operation of mutex, release the lock after reading and writing;
    • std::memory_order_acquire
      • for load();
      • Make sure that the read and write operations of all variables by the code after the current thread will not happen before the acquisition operation;
      • Equivalent to a barrier for read operations;
      • It can be seen that other threads write to this atomic variable and all previous atomic variables with release semantics, because acquire will read the latest memory value into this thread ;
      • Equivalent to the lock operation of mutex, obtain the lock and then read and write;
    • std::memory_order_acq_rel
      • At the same time, it has acquire semantics for reading and release semantics for writing;
      • You can see all writes that other threads impose release semantics, and at the same time all writes are visible to other threads that impose acquire semantics after their own release ends;
    • Reference blog: Memory Order (Memory Order) ;
  • An example is as follows:
    legend

  • A code example is as follows:

a = 0;
b = 0;
c = 0;

thread 1:
{
    
    
  a = 1;
  b.store(2, memory_order_relaxed);
  c.store(3, memory_order_release);
}

thread 2:
{
    
    
  while (c.load(memory_order_acquire) != 3)
    ;
  // 以下 assert 永远不会失败
  assert(a == 1 && b == 2);
  assert(b.load(memory_order_relaxed) == 2);
}
  • (3) Release/consumption model:
    • std::memory_order_consume
      • for load();
      • You can see all writes of release semantics imposed by other threads on the atomic variable and the atomic variables that have dependencies on the atomic variable;
      • is a slightly weaker version of the acquire semantics for reading;
      • This model is generally not recommended;
a = 0;
c = 0;

thread 1:
{
    
    
  a = 1;
  c.store(3, memory_order_release);
}

thread 2:
{
    
    
  while (c.load(memory_order_consume) != 3)
    ;
  assert(a == 1); // assert 可能失败也可能不失败
}
  • (4) Loose model:

    • std::memory_order_relaxed
    • The atomic operations in this thread are executed sequentially, but the order of atomic operations in different threads is arbitrary;
  • Some summaries are as follows:

Summarize

Summarize

Summarize

6.3 Spinlocks

  • It is used to implement atomic operations on shared resources in a multi-threaded environment in user mode . The principle of implementation is to use loops and CAS (Compare And Set) functions at the same time ;
  • Spin locks do not need to actually use mutexes to lock, nor are they an atomic operation, but they can achieve the effect of atomic operations;
  • std::atomicThere are two CAS functions in
    • 原子变量.compare_exchange_weak(期望值, 设置值)
      • Compare the atomic variable with the expected value, if they are equal, make the atomic variable equal to the set value and return true; otherwise, update the expected value to the atomic variable value and return false;
      • It is allowed to return false when the atomic variable is equal to the expected value because the competition fails , because there may be other threads that also have the atomic variable equal to the expected value but the competition succeeds and modify the value;
      • Low requirements for competition and higher efficiency ;
    • 原子变量.compare_exchange_strong(期望值, 设置值)
      • Compare the atomic variable with the expected value, if they are equal, make the atomic variable equal to the set value and return true; otherwise, update the expected value to the atomic variable value and return false;
      • It will not return false because the competition fails, if it returns false, it must be because the atomic variable value and the expected value are not equal;
      • Can solve competition problems well, but less efficiently ;
    • Some explanations of their differences are as follows:

the difference

  • An example of a counter implemented with a spin lock is as follows:
#include <atomic>
#include <thread>
#include <iostream>

int main() {
    
    
    std::atomic<int> count(0);
    std::thread t1([&]() {
    
    
        for (int i = 0; i < 1000000; ++i) {
    
    
        	/*以下是计数器自旋锁实现*/
            int expected = count.load();
            // 尝试将count设置为expected+1
            // 如果count == expected则设置成功,返回true
            // 否则,将expected设置为为count.load(),返回false
            while (!count.compare_exchange_weak(expected, expected + 1)) {
    
    }
        }
    });
    std::thread t2([&]() {
    
    
        for (int i = 0; i < 1000000; ++i) {
    
    
        	/*以下是计数器自旋锁实现*/
            int expected = count.load();
            while (!count.compare_exchange_weak(expected, expected + 1)) {
    
    }
        }
    });
    t1.join();
    t2.join();
    std::cout << "count = " << count.load() << '\n';
    return 0;
}

6. Others

1. long long int

  • At least 64bit int type;

[2]. noexcept

  • effect:

    • Declare that the current function cannot throw an exception;
    • If the declared function throws an exception, the program is terminated immediately;
    • Capable of preventing the spread of anomalies;
  • use:

    • 函数返回值类型 函数名(函数参数类型) noexcept;Used to declare that a function will not throw exceptions;
    • noexcept(表达式)Used to judge whether the expression is abnormal;
// 可能抛出异常的函数
void may_throw() {
    
    
	throw true;
}
auto non_block_throw = []{
    
    
	may_throw();
};

// 不抛出异常的函数
void no_throw() noexcept {
    
    
	return;
}
auto block_throw = []() noexcept {
    
    
	no_throw();
};

3. Custom String Literals

  • effect:

    • Avoid adding a lot of escape characters in the string, etc.;
  • use:

    • R"(字符串)"Defines a string as a string literal;
    • Overloading the double quote ""suffix operator can customize integer literals, floating point literals, string literals and character literals as string literals;
// 字符串字面量自定义必须设置如下的参数列表
std::string operator"" _wow1(const char *wow1, size_t len) {
    
    
	return std::string(wow1)+"woooooooooow, amazing";
}
std::string operator"" _wow2 (unsigned long long i) {
    
    
	return std::to_string(i)+"woooooooooow, amazing";
}
int main() {
    
    
	auto str = "abc"_wow1;
	auto num = 1_wow2;
	std::cout << str << std::endl;
	std::cout << num << std::endl;
	return 0;
}

some instructions

4. Control memory alignment

  • effect:

    • Query or re-modify the alignment of the structure;
  • use:

    • alignof(结构体): return the effective alignment value of the structure;
    • struct alignas(有效对齐值) 结构体名{};: Modify the effective alignment value of the structure, only aligning toward the larger;
    • Reference blog: alignof and alignas of C++11 ;
struct alignas(4) stTestAlign  // 修改有效对齐值为4字节
{
    
    
	char a;
	char b;
	stTestAlign()
	{
    
    
		cout << "sizeof(stTestAlign) =" << sizeof(stTestAlign) << endl;    //4
		cout << "alignof(stTestAlign) =" << alignof(stTestAlign) << endl;  //4
	}
};

struct stTestAlign
{
    
    
	char a;
	alignas(4) char b;  // char原本是1字节,强制作为4字节对齐
	stTestAlign()
	{
    
    
		cout << "sizeof(stTestAlign) =" << sizeof(stTestAlign) << endl;   //8
		cout << "alignof(stTestAlign) =" << alignof(stTestAlign) << endl; //4
	}
};

Supplement: default alignment of classes or structures

  • Reference blog: C++ memory alignment ;
  • concept:
    • Compiler alignment factor: #pragma pack(n), determined by the number of bits of the system and the compiler, usually the default is 8;
    • Valid alignment value: the minimum value between the alignment coefficient and the length of the longest data type in the structure ;
      • If the structure contains a member of the structure type, only the length of the longest data type inside the member is taken, that is to say , the length of the longest data type of the structure is determined by the length of the longest basic data type inside it ;
  • Alignment rules:
    • The offset of each member of the structure relative to the first address of the structure = an integer multiple of min{ effective alignment value , the length of the data type of the member }, and the gap between members will increase padding bytes;
    • The total size of the structure is an integer multiple of the effective alignment value , and the gap between members will increase padding bytes;

Seven, C++20 features

1. Coroutines

  • definition:

    • Coroutines are functions that can be suspended and resumed later;
    • It is completely a user-mode operation, and the switch does not need to fall into the kernel;
  • effect:

    • Can avoid callback hell, that is, the logic of the callback function is too deep and unclear;
    • Callback function: a function passed as a parameter to other functions for other functions to call, it can be an ordinary function or an anonymous function;
  • use:

    • The following three keywords can be used in the coroutine function:
    • co_await Awaitable结构体: Call an Awaitable object, and its internal definition determines whether it is suspended or continued and the behavior when it is suspended and resumed:
      • await_ready(): Ask whether the Awaitable structure is ready without waiting;
      • await_suspend(): Pass in a coroutine_handletype of parameter to suspend the Awaitable structure;
      • await_resume(): Call this function when the coroutine is re-running, and return the value at the same time, the Promise of the return value;
    • co_yield: Pause execution and return a value;
    • co_return: completes execution and returns a value;
// 用回调函数实现init + 100
using call_back = std::function<void(int)>;
void Add100ByCallback(int init, call_back f) //init是传入的初始值,add之后的结果由回调函数f通知
{
    
    
    std::thread t([init, f]() {
    
    
        std::this_thread::sleep_for(std::chrono::seconds(5)); // sleep一下,假装很耗时
        f(init + 100); // 耗时的计算完成了,调用回调函数
    });
    t.detach();
}

// 将回调函数封装成协程结构
struct Add100AWaitable
{
    
    
    Add100AWaitable(int init):init_(init) {
    
    }
    bool await_ready() const {
    
     return false; }
    int await_resume() {
    
     return result_; }
    void await_suspend(std::experimental::coroutine_handle<> handle)
    {
    
    
        // 定义一个回调函数,在此函数中恢复协程
        auto f = [handle, this](int value) mutable {
    
    
            result_ = value;
            handle.resume(); // 这句是关键
        };
        Add100ByCallback(init_, f); 
    }
    int init_; // 将参数存在这里
    int result_; // 将返回值存在这里
};

// 调用协程计算init + 100,可以多次调用
Task Add100ByCoroutine(int init, call_back f)
{
    
    
    int ret = co_await Add100AWaitable(init);
    ret = co_await Add100AWaitable(ret);
    ret = co_await Add100AWaitable(ret);
    f(ret);
}

2. Concepts and constraints

  • effect:

    • Allows the compiler to judge template parameters during compilation, thereby checking and restricting the use of template parameters;
  • use:

    • use conceptskeywords;

3. Module

  • effect:

    • Split the code into individual modules;
  • use:

    • Use keywords such as export, moduleand import;

Guess you like

Origin blog.csdn.net/weixin_43992162/article/details/129240941