参考手册
https://www.apiref.com/cpp-zh/index.html c++ 中文参考手册
https://zh.cppreference.com/w/cpp c++ 参考手册
基本概念
https://zh.cppreference.com/w/cpp/language/basic_concepts
本节定义了描述 C++ 编程语言时所使用的一些专门的术语与概念。
一个 C++ 程序是一个含有声明的文本文件(通常为头文件与源文件)序列。它们被翻译成一个可执行程序,C++ 实现通过调用其主 (main) 函数执行这一程序。
在 C++ 程序中,一些被称为关键词的词语有着特殊的含义。其它词语可以被用作标识符。在翻译的过程中,注释会被忽略。程序中的某些特定字符必须通过转义序列表示。
C++ 程序中的实体包括值、对象、引用、 结构化绑定 (C++17 起)、函数、枚举项、类型、类成员、模板、模板特化、命名空间和形参包。预处理器宏不是 C++ 实体。
声明可以引入实体,将它们与名字关联起来,并定义其属性。能够定义使用一个实体所需的所有属性的声明是定义。对任何被 ODR 使用的非内联函数或变量,程序中必须只含有其一个定义。
函数的定义通常包括一系列的语句,其中部分会包含表达式。表达式指定了程序需要进行的运算。
程序中遇到的名字通过名字查找与引入它们的声明关联起来。每个名字都只在称为其作用域的程序部分中有效。有些名字有链接,这使得它们即使出现在不同的作用域或翻译单元时也代表相同的实体。
C++ 中的每一个对象、引用、函数和表达式都会关联一个类型。类型可以分为基础类型,复合类型,或自定义类型,以及完整或不完整的类型等。
被声明的且不是非静态数据成员的对象和引用是变量。
记录
1. 模板
模板概念 :https://zh.cppreference.com/w/cpp/language/templates
模板类继承
1.模板类从一个父模板类继承后,不能访问其内部的protected成员变量,提示:not declare;
2. 普通类从一个父模板类继承后,可以访问其内部的protected成员变量,可正常编译和使用;
对于第1个现象,如果想正常使用需要加上父模板类的域名;
具体实例 参考博文 : C++ 模板类继承,成员访问问题_gtl_csdn的博客-CSDN博客
2. virtual 函数说明符
https://zh.cppreference.com/w/cpp/language/virtual
virtual 说明符指定非静态成员函数为虚函数并支持动态调用派发。它只能在非静态成员函数的首个声明(即当它于类定义中声明时)的 声明说明符序列 中出现。
解释
虚函数是可在派生类中覆盖其行为的成员函数。与非虚函数相反,即使没有关于该类实际类型的编译时信息,仍然保留被覆盖的行为。当使用到基类的指针或引用来处理派生类时,对被覆盖的虚函数的调用,将会调用定义于派生类中的行为。当使用有限定名字查找(即函数名出现在作用域解析运算符 ::
的右侧)时,此行为被抑制。
#include <iostream>
struct Base {
virtual void f() {
std::cout << "base\n";
}
};
struct Derived : Base {
void f() override { // 'override' 可选
std::cout << "derived\n";
}
};
int main()
{
Base b;
Derived d;
// 通过引用调用虚函数
Base& br = b; // br 的类型是 Base&
Base& dr = d; // dr 的类型也是 Base&
br.f(); // 打印 "base"
dr.f(); // 打印 "derived"
// 通过指针调用虚函数
Base* bp = &b; // bp 的类型是 Base*
Base* dp = &d; // dp 的类型也是 Base*
bp->f(); // 打印 "base"
dp->f(); // 打印 "derived"
// 非虚函数调用
br.Base::f(); // 打印 "base"
dr.Base::f(); // 打印 "base"
}
每个虚函数都有其最终覆盖函数,它是进行虚函数调用时所执行的函数,不管是否可见或者不可访问。基类 Base
的虚成员函数 vf
是最终覆盖函数,除非派生类声明或(通过多重继承)继承了覆盖 vf
的另一个函数。
3. 函数包装器
std::function
std::function - cppreference.com
定义于头文件 | ||
template< class > | (C++11 起) | |
template< class R, class... Args > | (C++11 起) | |
类模板 std::function
是通用多态函数封装器。 std::function
的实例能存储、复制及调用任何可复制构造 (CopyConstructible) 的可调用 (Callable) 目标——函数、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
存储的可调用对象被称为 std::function
的目标。若 std::function
不含目标,则称它为空。调用空 std::function
的目标导致抛出 std::bad_function_call 异常。
std::function
满足可复制构造 (CopyConstructible) 和可复制赋值 (CopyAssignable) 。
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
// 存储自由函数
std::function<void(int)> f_display = print_num;
f_display(-9);
// 存储 lambda
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
// 存储到 std::bind 调用的结果
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
// 存储到成员函数的调用
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
// 存储到数据成员访问器的调用
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
// 存储到成员函数及对象的调用
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);
// 存储到成员函数和对象指针的调用
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);
// 存储到函数对象的调用
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
auto factorial = [](int n) {
// 存储 lambda 对象以模拟“递归 lambda ”,注意额外开销
std::function<int(int)> fac = [&](int n){ return (n < 2) ? 1 : n*fac(n-1); };
// note that "auto fac = [&](int n){...};" does not work in recursive calls
return fac(n);
};
for (int i{5}; i != 8; ++i) { std::cout << i << "! = " << factorial(i) << "; "; }
}
std::function 经常作为回调函数使用。
1. 作为回调函数
C++ 回调函数的实现,以及function,bind,lambda表达式的使用
C++11的无固定形参的回调
https://blog.csdn.net/qq_28107929/article/details/89680924
4. lambda 表达式
https://zh.cppreference.com/w/cpp/language/lambda
构造闭包:能够捕获作用域中的变量的无名函数对象。
语法
[ 捕获 ] <模板形参>(可选)(C++20) ( 形参 ) lambda-说明符 { 函数体 } | (1) | ||||||||
[ 捕获 ] ( 形参 ) 尾随返回类型 { 函数体 } | (2) | ||||||||
[ 捕获 ] ( 形参 ) { 函数体 } | (3) | ||||||||
[ 捕获 ] lambda-说明符(可选)(C++23) { 函数体 } | (4) | ||||||||
1) 完整声明。
2) const lambda 的声明:复制捕获的对象在 lambda 体内为 const。
3) 省略 尾随返回类型 :闭包的 operator()
的返回类型从 return 语句推导,如同对于声明返回类型为 auto 的函数的推导一样。
4) 省略形参列表:不接收实参的函数,如同形参列表是 ()
。仅当完全不使用 constexpr
、consteval
、mutable
、异常说明、属性或尾随返回类型时才能使用此形式。 (C++23 前)
解释
捕获 | - | 零或更多捕获符的逗号分隔列表,可选地以 默认捕获符(capture-default) 起始。 有关捕获符的详细描述,见下文。 若变量满足下列条件,则 lambda 表达式可以不捕获就使用它 若变量满足下列条件,则 lambda 表达式可以不捕获就读取其值
|
Lambda 捕获
捕获 是零或更多捕获符的逗号分隔列表,可选地以 默认捕获符 开始。仅有的默认捕获符是
&
(以引用隐式捕获被使用的自动变量)和=
(以复制隐式捕获被使用的自动变量)。
当出现任一默认捕获符时,都能隐式捕获当前对象(*this
)。当它被隐式捕获时,始终被以引用捕获,即使默认捕获符是 =
也是如此。当默认捕获符为 =
时,*this
的隐式捕获被弃用。 (C++20 起)
捕获 中单独的捕获符的语法是
标识符 | (1) | ||||||||
标识符 ... | (2) | ||||||||
标识符 初始化器 | (3) | (C++14 起) | |||||||
& 标识符 | (4) | ||||||||
& 标识符 ... | (5) | ||||||||
& 标识符 初始化器 | (6) | (C++14 起) | |||||||
this | (7) | ||||||||
* this | (8) | (C++17 起) | |||||||
... 标识符 初始化器 | (9) | (C++20 起) | |||||||
& ... 标识符 初始化器 | (10) | (C++20 起) | |||||||
1) 简单以复制捕获
2) 作为包展开的简单以复制捕获
3) 带初始化器的以复制捕获
4) 简单以引用捕获
5) 作为包展开的简单引用捕获
6) 带初始化器的以引用捕获
7) 当前对象的简单以引用捕获
8) 当前对象的简单以复制捕获
9) 用作为包展开的初始化器以复制捕获
10) 用作为包展开的初始化器以引用捕获
当默认捕获符是 &
时,后继的简单捕获符必须不以 &
开始。
struct S2 { void f(int i); };
void S2::f(int i)
{
[&]{}; // OK:默认以引用捕获
[&, i]{}; // OK:以引用捕获,但 i 以值捕获
[&, &i] {}; // 错误:以引用捕获为默认时的以引用捕获
[&, this] {}; // OK:等价于 [&]
[&, this, i]{}; // OK:等价于 [&, i]
}
当默认捕获符是 =
时,后继的简单捕获符必须以 &
开始,或者为 *this
(C++17 起) 或 this
(C++20 起)。
struct S2 { void f(int i); };
void S2::f(int i)
{
[=]{}; // OK:默认以复制捕获
[=, &i]{}; // OK:以复制捕获,但 i 以引用捕获
[=, *this]{}; // C++17 前:错误:无效语法
// C++17 起:OK:以复制捕获外围的 S2
[=, this] {}; // C++20 前:错误:= 为默认时的 this
// C++20 起:OK:同 [=]
}
任何捕获符只可以出现一次:
struct S2 { void f(int i); };
void S2::f(int i)
{
[i, i] {}; // 错误:i 重复
[this, *this] {}; // 错误:"this" 重复 (C++17)
}
5. std::bind
定义于头文件 | ||
(1) | ||
template< class F, class... Args > | (C++11 起) (C++20 前) | |
template< class F, class... Args > | (C++20 起) | |
(2) | ||
template< class R, class F, class... Args > | (C++11 起) (C++20 前) | |
template< class R, class F, class... Args > | (C++20 起) | |
函数模板 bind
生成 f
的转发调用包装器。调用此包装器等价于以一些绑定到 args
的参数调用 f
。
参数
f | - | 可调用 (Callable) 对象(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针) |
args | - | 要绑定的参数列表,未绑定参数为命名空间 std::placeholders 的占位符 _1, _2, _3... 所替换 |
返回值
返回类型: 成员对象,构造函数
注解
如可调用 (Callable) 中描述,调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与 std::unique_ptr),指向将访问其成员的对象。
到 bind 的参数被复制或移动,而且决不按引用传递,除非包装于 std::ref 或 std::cref 。
允许同一 bind 表达式中的多重占位符(例如多个 _1
),但结果仅若对应参数( u1
)是左值或不可移动右值才良好定义。
示例
#include <random>
#include <iostream>
#include <memory>
#include <functional>
void f(int n1, int n2, int n3, const int& n4, int n5)
{
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
int g(int n1)
{
return n1;
}
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
int main()
{
using namespace std::placeholders; // 对于 _1, _2, _3...
// 演示参数重排序和按引用传递
int n = 7;
// ( _1 与 _2 来自 std::placeholders ,并表示将来会传递给 f1 的参数)
auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
n = 10;
f1(1, 2, 1001); // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001
// 进行到 f(2, 42, 1, n, 7) 的调用
// 嵌套 bind 子表达式共享占位符
auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
f2(10, 11, 12); // 进行到 f(12, g(12), 12, 4, 5); 的调用
// 常见使用情况:以分布绑定 RNG
std::default_random_engine e;
std::uniform_int_distribution<> d(0, 10);
std::function<int()> rnd = std::bind(d, e); // e 的一个副本存储于 rnd
for(int n=0; n<10; ++n)
std::cout << rnd() << ' ';
std::cout << '\n';
// 绑定指向成员函数指针
Foo foo;
auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
f3(5);
// 绑定指向数据成员指针
auto f4 = std::bind(&Foo::data, _1);
std::cout << f4(foo) << '\n';
// 智能指针亦能用于调用被引用对象的成员
std::cout << f4(std::make_shared<Foo>(foo)) << '\n'
<< f4(std::make_unique<Foo>(foo)) << '\n';
}
6. 使用std::function、std::bind和Lambda实现c++的回调函数
下文皆参考一下几篇博文
参考文档: https://blog.csdn.net/fantasysolo/article/details/90698874
使用std::function作为函数入参
https://www.jianshu.com/p/c4c84b073413
C++使用模板、函数指针、接口和lambda表达式这四种方法做回调函数的区别比较
https://www.cnblogs.com/kanite/p/8299147.html
c++ 传统回调函数实现
1. 函数指针
// CppTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
class Result;
typedef void (Result::*CallbackPtr)(int);
class MathCallBack
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,Result *caller,CallbackPtr callback)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
(caller->*callback)(result);
}
};
class Result
{
public:
void showResult(int res)
{
printf("result = %d\n",res);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Result reShow;
MathCallBack math;
math.Add(1,3,&reShow,&Result::showResult);
system("pause");
return 0;
}
2. 接口类
// CppTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
template<typename T>
class MathTemplate
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,T callback)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
callback.showResult(result);
}
};
class Result
{
public:
void showResult(int res)
{
printf("result = %d\n",res);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Result reShow;
MathTemplate<Result> math;
math.Add(1,3,reShow);
system("pause");
return 0;
}
3. 模板
// CppTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
template<typename T>
class MathTemplate
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,T callback)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
callback.showResult(result);
}
};
class Result
{
public:
void showResult(int res)
{
printf("result = %d\n",res);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Result reShow;
MathTemplate<Result> math;
math.Add(1,3,reShow);
system("pause");
return 0;
}
这三种方法的确定是 耦合度比较高,因此需要使用C++ 11 提供的std::function 方法。
4. 利用std::function 和Lambda 实现回调函数
// CppTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
#include <iostream>
#include <functional>
class MathCallBack
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,std::function<void (int)> func)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
func(result);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
MathCallBack math;
math.Add(1,3,[](int result) -> void {
printf("result = %d\n",result);
});
system("pause");
return 0;
}
6. 使用std::function和std::bind实现回调
参见 std::bind 示例代码。
现代C++的回调技术--使用std::bind和std::function
https://blog.csdn.net/will_free/article/details/61622122
#include<iostream>
#include<functional>
typedef std::function<void()> Functor;
class Blas
{
public:
void setCallBack(const Functor& cb)
{functor = cb;};
void printFunctor()
{functor();};
private:
Functor functor;
};
class Atlas
{
public:
Atlas(int x_) : x(x_)
{
//使用当前类的静态成员函数
blas.setCallBack(std::bind(&addStatic,x,2));
//使用当前类的非静态成员函数
blas.setCallBack(std::bind(&Atlas::add,this,x,2));
}
void print()
{
blas.printFunctor();
}
private:
void add(int a,int b)
{
std::cout << a+b << std::endl;
}
static void addStatic(int a,int b)
{
std::cout << a+b << std::endl;
}
Blas blas;
int x;
};
int main(int argc,char** argv)
{
Atlas atlas(5);
atlas.print();
return 0;
}
7. 多线程
https://zh.cppreference.com/w/cpp/thread
线程
https://zh.cppreference.com/w/cpp/thread/thread
std::thread
定义于头文件 | ||
class thread; | (C++11 起) | |
类 thread
表示单个执行线程。线程允许多个函数同时执行。
线程在构造关联的线程对象时立即开始执行(等待任何OS调度延迟),从提供给作为构造函数参数的顶层函数开始。顶层函数的返回值将被忽略,而且若它以抛异常终止,则调用 std::terminate 。顶层函数可以通过 std::promise 或通过修改共享变量(可能需要同步,见 std::mutex 与 std::atomic )将其返回值或异常传递给调用方。
std::thread
对象也可能处于不表示任何线程的状态(默认构造、被移动、 detach 或 join 后),并且执行线程可能与任何 thread
对象无关( detach 后)。
没有两个 std::thread
对象会表示同一执行线程; std::thread
不是可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的,尽管它可移动构造 (MoveConstructible) 且可移动赋值 (MoveAssignable) 。
std::thread
thread() noexcept; | (1) | (C++11 起) |
thread( thread&& other ) noexcept; | (2) | (C++11 起) |
template< class Function, class... Args > | (3) | (C++11 起) |
thread( const thread& ) = delete; | (4) | (C++11 起) |
构造新的 thread
对象。
1) 构造不表示线程的新 thread
对象。
2) 移动构造函数。构造表示曾为 other
所表示的执行线程的 thread
对象。此调用后 other
不再表示执行线程。
3) 构造新的 std::thread
对象并将它与执行线程关联。新的执行线程开始执行
std::invoke(decay_copy(std::forward<Function>(f)),
decay_copy(std::forward<Args>(args))...);
其中 decay_copy
定义为
template <class T>
std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }
除了 decay_copy
的调用在调用方语境求值,故而任何求值和复制/移动参数中抛出的异常被抛到当前线程,而不用开始新线程。
构造函数的调用完成同步于(定义于 std::memory_order )新的执行线程上 f 副本的调用开始。
若 std::decay<Function>::type 与 std::thread 为同一类型,则此构造函数不参与重载决议。
4) 复制构造函数被删除; thread
不可复制。没有二个 std::thread
对象可表示同一执行线程。
参数
other | - | 用以构造此 thread 的另一 thread 对象 |
f | - | 执行于新线程的可调用 (Callable) |
args... | - | 传递给新函数的参数 |
注解
移动或按值复制线程函数的参数。若需要传递引用参数给线程函数,则必须包装它(例如用 std::ref 或 std::cref )忽略来自函数的任何返回值.。若函数抛异常,则调用 std::terminate 。为将返回值或异常传递回调用方线程,可使用 std::promise 或 std::async 。
示例
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
void f1(int n)
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 1 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void f2(int& n)
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 2 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
class foo
{
public:
void bar()
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 3 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int n = 0;
};
class baz
{
public:
void operator()()
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 4 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int n = 0;
};
int main()
{
int n = 0;
foo f;
baz b;
std::thread t1; // t1 不是线程
std::thread t2(f1, n + 1); // 按值传递
std::thread t3(f2, std::ref(n)); // 按引用传递
std::thread t4(std::move(t3)); // t4 现在运行 f2() 。 t3 不再是线程
std::thread t5(&foo::bar, &f); // t5 在对象 f 上运行 foo::bar()
std::thread t6(b); // t6 在对象 b 的副本上运行 baz::operator()
t2.join();
t4.join();
t5.join();
t6.join();
std::cout << "Final value of n is " << n << '\n';
std::cout << "Final value of f.n (foo::n) is " << f.n << '\n';
std::cout << "Final value of b.n (baz::n) is " << b.n << '\n';
}
互斥
互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。
定义于头文件 | |
(C++11) | 提供基本互斥设施 (类) |
(C++11) | 提供互斥设施,实现有时限锁定 (类) |
(C++11) | 提供能被同一线程递归锁定的互斥设施 (类) |
(C++11) | 提供能被同一线程递归锁定的互斥设施,并实现有时限锁定 (类) |
定义于头文件 | |
(C++17) | 提供共享互斥设施 (类) |
(C++14) | 提供共享互斥设施并实现有时限锁定 (类) |
通用互斥管理 | |
定义于头文件 | |
(C++11) | 实现严格基于作用域的互斥体所有权包装器 (类模板) |
(C++17) | 用于多个互斥体的免死锁 RAII 封装器 (类模板) |
(C++11) | 实现可移动的互斥体所有权包装器 (类模板) |
(C++14) | 实现可移动的共享互斥体所有权封装器 (类模板) |
defer_lock_ttry_to_lock_tadopt_lock_t (C++11)(C++11)(C++11) | 用于指定锁定策略的标签类型 (类) |
defer_locktry_to_lockadopt_lock (C++11)(C++11)(C++11) | 用于指定锁定策略的标签常量 (常量) |
通用锁定算法 | |
(C++11) | 试图通过重复调用 try_lock 获得互斥体的所有权(函数模板) |
(C++11) | 锁定指定的互斥体,若任何一个不可用则阻塞 (函数模板) |
单次调用 | |
(C++11) | 确保 call_once 只调用函数一次的帮助对象 (类) |
(C++11) | 仅调用函数一次,即使从多个线程调用 (函数模板) |
https://zh.cppreference.com/w/cpp/thread/mutex
https://zh.cppreference.com/w/cpp/thread/unique_lock
https://zh.cppreference.com/w/cpp/thread/lock_guard
通常不直接使用 std::mutex
,使用 std::unique_lock 、 std::lock_guard 或 std::scoped_lock (C++17 起)以更加异常安全的方式管理锁定。
std::lock_guard
是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。
创建 lock_guard
对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard
对象的作用域时,销毁 lock_guard
并释放互斥。
std::unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
类 unique_lock 可移动,但不可复制——它满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。
类 unique_lock 满足基本可锁定 (BasicLockable) 要求。若 Mutex 满足可锁定 (Lockable) 要求,则 unique_lock 亦满足可锁定 (Lockable) 要求(例如:能用于 std::lock ) ;若 Mutex 满足可定时锁定 (TimedLockable) 要求,则 unique_lock 亦满足可定时锁定 (TimedLockable) 要求。
std::unique_lock 比std::mutex 的锁定方式更多,增加可定时锁定。
条件变量
条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。条件变量始终关联到一个互斥。
定义于头文件 | |
(C++11) | 提供与 std::unique_lock 关联的条件变量 (类) |
(C++11) | 提供与任何锁类型关联的条件变量 (类) |
(C++11) | 安排到在此线程完全结束时对 notify_all 的调用(函数) |
(C++11) | 列出条件变量上定时等待的可能结果 (枚举) |
std::condition_variable
https://zh.cppreference.com/w/cpp/thread/condition_variable
定义于头文件 | ||
class condition_variable; | (C++11 起) | |
condition_variable
类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable
。
有意修改变量的线程必须
- 获得
std::mutex
(常通过 std::lock_guard ) - 在保有锁时进行修改
- 在
std::condition_variable
上执行 notify_one 或 notify_all (不需要为通知保有锁)
即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。
任何有意在 std::condition_variable
上等待的线程必须
- 在与用于保护共享变量者相同的互斥上获得 std::unique_lock<std::mutex>
- 执行下列之一:
- 检查条件,是否为已更新或提醒它的情况
- 执行 wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行。
- condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。
或者
- 使用 wait 、 wait_for 及 wait_until 的有谓词重载,它们包揽以上三个步骤
std::condition_variable
只可与 std::unique_lock<std::mutex> 一同使用;此限制在一些平台上允许最大效率。 std::condition_variable_any 提供可与任何基本可锁定 (BasicLockable) 对象,例如 std::shared_lock 一同使用的条件变量。
condition_variable 容许 wait 、 wait_for 、 wait_until 、 notify_one 及 notify_all 成员函数的同时调用。
类 std::condition_variable
是标准布局类型 (StandardLayoutType) 。它非可复制构造 (CopyConstructible) 、可移动构造 (MoveConstructible) 、可复制赋值 (CopyAssignable) 或可移动赋值 (MoveAssignable) 。
通知
通知一个等待的线程 (公开成员函数) | |
通知所有等待的线程 (公开成员函数) |
等待
阻塞当前线程,直到条件变量被唤醒 (公开成员函数) | |
阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后 (公开成员函数) | |
阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点 (公开成员函数) |
示例
与 std::mutex 组合使用 condition_variable
,以促进线程间交流。
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// 等待直至 main() 发送数据
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// 等待后,我们占有锁。
std::cout << "Worker thread is processing data\n";
data += " after processing";
// 发送数据回 main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// 发送数据到 worker 线程
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// 等候 worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
std::condition_variable::wait
https://zh.cppreference.com/w/cpp/thread/condition_variable/wait
std::condition_variable::wait_for
https://zh.cppreference.com/w/cpp/thread/condition_variable/wait_for
std::condition_variable::wait_until
https://zh.cppreference.com/w/cpp/thread/condition_variable/wait_until
template< class Clock, class Duration > std::cv_status | (1) | (C++11 起) |
template< class Clock, class Duration, class Pred > bool wait_until( std::unique_lock<std::mutex>& lock, | (2) | (C++11 起) |
wait_until
导致当前线程阻塞直至通知条件变量、抵达指定时间或虚假唤醒发生,可选的循环直至满足某谓词。
1) 原子地释放 lock
,阻塞当前线程,并将它添加到等待在 *this 上的线程列表。将在执行 notify_all() 或 notify_one() 时,或抵达绝对时间点 timeout_time
时解除阻塞线程。亦可能虚假地解除阻塞。解除阻塞时,无关缘由,重获得 lock
并退出 wait_for()
。
2) 等价于
while (!pred()) {
if (wait_until(lock, timeout_time) == std::cv_status::timeout) {
return pred();
}
}
return true;
示例:
#include <iostream>
#include <atomic>
#include <condition_variable>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
std::condition_variable cv;
std::mutex cv_m;
std::atomic<int> i{0};
void waits(int idx)
{
std::unique_lock<std::mutex> lk(cv_m);
auto now = std::chrono::system_clock::now();
if(cv.wait_until(lk, now + idx*100ms, [](){return i == 1;}))
std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n';
else
std::cerr << "Thread " << idx << " timed out. i == " << i << '\n';
}
void signals()
{
std::this_thread::sleep_for(120ms);
std::cerr << "Notifying...\n";
cv.notify_all();
std::this_thread::sleep_for(100ms);
i = 1;
std::cerr << "Notifying again...\n";
cv.notify_all();
}
int main()
{
std::thread t1(waits, 1), t2(waits, 2), t3(waits, 3), t4(signals);
t1.join();
t2.join();
t3.join();
t4.join();
}
Future
标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以写入其返回值或存储异常,而且可以由持有该引用该共享态的 std::future 或 std::shared_future 实例的线程检验、等待或是操作这个状态。
定义于头文件 | |
(C++11) | 存储一个值以进行异步获取 (类模板) |
(C++11) | 打包一个函数,存储其返回值以进行异步获取 (类模板) |
(C++11) | 等待被异步设置的值 (类模板) |
(C++11) | 等待被异步设置的值(可能为其他 future 所引用) (类模板) |
(C++11) | 异步运行一个函数(有可能在新线程中执行),并返回保有其结果的 std::future (函数模板) |
(C++11) | 指定 std::async 所用的运行策略 (枚举) |
(C++11) | 指定在 std::future 和 std::shared_future 上的定时等待的结果 (枚举) |
std::promise
定义于头文件 | ||
template< class R > class promise; | (1) | (C++11 起) |
template< class R > class promise<R&>; | (2) | (C++11 起) |
template<> class promise<void>; | (3) | (C++11 起) |
1) 空模板
2) 非 void 特化,用于在线程间交流对象
3) void 特化,用于交流无状态事件
类模板 std::promise
提供存储值或异常的设施,之后通过 std::promise
对象所创建的 std::future 对象异步获得结果。注意 std::promise
只应当使用一次。
每个 promise 与共享状态关联,共享状态含有一些状态信息和可能仍未求值的结果,它求值为值(可能为 void )或求值为异常。 promise 可以对共享状态做三件事:
- 使就绪: promise 存储结果或异常于共享状态。标记共享状态为就绪,并解除阻塞任何等待于与该共享状态关联的 future 上的线程。
- 释放: promise 放弃其对共享状态的引用。若这是最后一个这种引用,则销毁共享状态。除非这是 std::async 所创建的未就绪的共享状态,否则此操作不阻塞。
- 抛弃: promise 存储以 std::future_errc::broken_promise 为 error_code 的 std::future_error 类型异常,令共享状态为就绪,然后释放它。
promise 是 promise-future 交流通道的“推”端:存储值于共享状态的操作同步于(定义于 std::memory_order )任何在共享状态上等待的函数(如 std::future::get )的成功返回。其他情况下对共享状态的共时访问可能冲突:例如, std::shared_future::get 的多个调用方必须全都是只读,或提供外部同步。
成员函数
获取结果
返回与承诺的结果关联的 future (公开成员函数) |
设置结果
设置结果为指定值 (公开成员函数) | |
设置结果为指定值,同时仅在线程退出时分发提醒 (公开成员函数) |
示例
此示例展示能如何将 promise<int>
用作线程间信号。
#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>
void accumulate(std::vector<int>::iterator first,
std::vector<int>::iterator last,
std::promise<int> accumulate_promise)
{
int sum = std::accumulate(first, last, 0);
accumulate_promise.set_value(sum); // 提醒 future
}
void do_work(std::promise<void> barrier)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
barrier.set_value();
}
int main()
{
// 演示用 promise<int> 在线程间传递结果。
std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };
std::promise<int> accumulate_promise;
std::future<int> accumulate_future = accumulate_promise.get_future();
std::thread work_thread(accumulate, numbers.begin(), numbers.end(),
std::move(accumulate_promise));
// future::get() 将等待直至该 future 拥有合法结果并取得它
// 无需在 get() 前调用 wait()
//accumulate_future.wait(); // 等待结果
std::cout << "result=" << accumulate_future.get() << '\n';
work_thread.join(); // wait for thread completion
// 演示用 promise<void> 在线程间对状态发信号
std::promise<void> barrier;
std::future<void> barrier_future = barrier.get_future();
std::thread new_work_thread(do_work, std::move(barrier));
barrier_future.wait();
new_work_thread.join();
}
std::packaged_task
定义于头文件 | ||
template< class > class packaged_task; // 不定义 | (1) | (C++11 起) |
template< class R, class ...Args > | (2) | (C++11 起) |
类模板 std::packaged_task
包装任何可调用 (Callable) 目标(函数、 lambda 表达式、 bind 表达式或其他函数对象),使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。
正如 std::function , std::packaged_task
是多态、具分配器的容器:可在堆上或以提供的分配器分配存储的可调用对象。
获取结果
返回与承诺的结果关联的 std::future (公开成员函数) |
执行
执行函数 (公开成员函数) | |
执行函数,并确保结果仅在一旦当前线程退出时就绪 (公开成员函数) | |
重置状态,抛弃任何先前执行的存储结果 (公开成员函数) |
示例
#include <iostream>
#include <cmath>
#include <thread>
#include <future>
#include <functional>
// 避免对 std::pow 重载集消歧义的独有函数
int f(int x, int y) { return std::pow(x,y); }
void task_lambda()
{
std::packaged_task<int(int,int)> task([](int a, int b) {
return std::pow(a, b);
});
std::future<int> result = task.get_future();
task(2, 9);
std::cout << "task_lambda:\t" << result.get() << '\n';
}
void task_bind()
{
std::packaged_task<int()> task(std::bind(f, 2, 11));
std::future<int> result = task.get_future();
task();
std::cout << "task_bind:\t" << result.get() << '\n';
}
void task_thread()
{
std::packaged_task<int(int,int)> task(f);
std::future<int> result = task.get_future();
std::thread task_td(std::move(task), 2, 10);
task_td.join();
std::cout << "task_thread:\t" << result.get() << '\n';
}
int main()
{
task_lambda();
task_bind();
task_thread();
}
std::future
定义于头文件 | ||
template< class T > class future; | (1) | (C++11 起) |
template< class T > class future<T&>; | (2) | (C++11 起) |
template<> class future<void>; | (3) | (C++11 起) |
类模板 std::future
提供访问异步操作结果的机制:
- (通过 std::async 、 std::packaged_task 或 std::promise 创建的)异步操作能提供一个
std::future
对象给该异步操作的创建者。
- 然后,异步操作的创建者能用各种方法查询、等待或从
std::future
提取值。若异步操作仍未提供值,则这些方法可能阻塞。
- 异步操作准备好发送结果给创建者时,它能通过修改链接到创建者的
std::future
的共享状态(例如 std::promise::set_value )进行。
注意, std::future
所引用的共享状态不与另一异步返回对象共享(与 std::shared_future 相反)。
获取结果
返回结果 (公开成员函数) |
状态
检查 future 是否拥有共享状态 (公开成员函数) | |
等待结果变得可用 (公开成员函数) | |
等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回。 (公开成员函数) | |
等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回。 (公开成员函数) |
示例
#include <iostream>
#include <future>
#include <thread>
int main()
{
// 来自 packaged_task 的 future
std::packaged_task<int()> task([](){ return 7; }); // 包装函数
std::future<int> f1 = task.get_future(); // 获取 future
std::thread(std::move(task)).detach(); // 在线程上运行
// 来自 async() 的 future
std::future<int> f2 = std::async(std::launch::async, [](){ return 8; });
// 来自 promise 的 future
std::promise<int> p;
std::future<int> f3 = p.get_future();
std::thread( [&p]{ p.set_value_at_thread_exit(9); }).detach();
std::cout << "Waiting..." << std::flush;
f1.wait();
f2.wait();
f3.wait();
std::cout << "Done!\nResults are: "
<< f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
}
线程池
https://github.com/progschj/ThreadPool/blob/master/ThreadPool.h
https://blog.csdn.net/weixin_41074793/article/details/104302557
线程池-学习笔记(ThreadPool源代码内容详细解读)
https://www.cnblogs.com/ailumiyana/p/10016965.html
基于C++11实现线程池的工作原理
https://zhuanlan.zhihu.com/p/374242822
C/C++手撕线程池(线程池的封装和实现)
https://zhuanlan.zhihu.com/p/92632090
面试官:来!聊聊线程池的实现原理以及使用时的问题
在线程池中存在几个概念:核心线程数、最大线程数、任务队列。核心线程数指的是线程池的基本大小;最大线程数指的是,同一时刻线程池中线程的数量最大不能超过该值;任务队列是当任务较多时,线程池中线程的数量已经达到了核心线程数,这时候就是用任务队列来存储我们提交的任务。 与其他池化技术不同的是,线程池是基于生产者-消费者
模式来实现的,任务的提交方是生产者,线程池是消费者。当我们需要执行某个任务时,只需要把任务扔到线程池中即可。线程池中执行任务的流程如下图如下。
https://github.com/progschj/ThreadPool/blob/master/ThreadPool.h
代码解释
https://www.cnblogs.com/oloroso/p/5881863.html
https://segmentfault.com/a/1190000022456590
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
// 线程类
class ThreadPool {
public:
ThreadPool(size_t);
// 构造函数,传入线程数目
// std::size_t 是 sizeof 运算符还有 sizeof... 运算符和 alignof 运算符 (C++11 起)所返回的
// 无符号整数类型。std::size_t 的位宽不小于 16 。(C++11 起)
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
// 入队任务(传入函数和函数的参数)
// 这个函数模块只适用于c++ 11, std::result_of 在c++ 17 弃用
// 一个enqueue模板函数 适用于任何函数(变参、成员都可以),返回F(Args...)函数的运行结果,通过std::future<type>返回,然后这个type又
// 利用了运行时检测(还是编译时检测?)推断出来的
// std::future 类模板 std::future 提供访问异步操作结果的机制:这里哪里来的异步操作??
// typename C++ 关键词:typename,在模板的声明或定义内,typename 可用于声明某个待决的有限定名是类型。
// std::result_of 在编译时推导 INVOKE 表达式的返回类型。::type 是其成员类型,表示其返回的类型
// std::result_of 在c++7 中弃用,invoke_result 代替
// auto 占位符 对于变量,指定要从其初始化器自动推导出其类型
// 对于函数,指定要从其 return 语句推导出其返回类型
~ThreadPool();
private:
// need to keep track of threads so we can join them
// 工作线程
std::vector< std::thread > workers;
// the task queue
// 任务队列
// std::function<void()> ???如何实现都是 void() 函数签名
std::queue< std::function<void()> > tasks;
// synchronization
// 互斥
std::mutex queue_mutex;
// 条件变量
std::condition_variable condition;
// 是否停止
bool stop;
};
// the constructor just launches some amount of workers
// 构造函数,传入线程数目 threads
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for(size_t i = 0;i<threads;++i)
workers.emplace_back(
[this]
// 一个lambda,值传递方式,异步函数
{
for(;;)
{
std::function<void()> task;
{
// lock
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock,
[this]{ return this->stop || !this->tasks.empty(); });
// 条件变量,
// this->stop || !this->tasks.empty() = false时 wait阻塞
if(this->stop && this->tasks.empty())
return;
// stop && empty 时 退出
// 退出之时,哪里unlock??
task = std::move(this->tasks.front());
// 从tasks 队首取的 一个task
// std::move
// 左值,右值
this->tasks.pop();
// 从tasks队列 删除
}
task();
// task 执行
}
}
);
}
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
// 得到返回类型
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
// auto
// std::make_shared 可以返回一个指定类型的 std::shared_ptr
// std::packaged_task: 使用std::packaged_task 包装 std::bind 表达式
// std::bind 使用std::bind 绑定 函数f 和参数,
// std::forward
//
std::future<return_type> res = task->get_future();
// 通过task->get_future() 获得task运行结果
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
// 将task 推入队列
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
#endif
workerThread
并发
值类别
https://zh.cppreference.com/w/cpp/language/value_category
每个 C++ 表达式(带有操作数的操作符、字面量、变量名等)可按照两种独立的特性加以辨别:类型和值类别 (value category)。每个表达式都具有某种非引用类型,且每个表达式只属于三种基本值类别中的一种:纯右值 (prvalue)、亡值 (xvalue)、左值 (lvalue)。
- 泛左值 (glvalue)(“泛化 (generalized)”的左值)是其求值确定一个对象、位域或函数的个体的表达式;
- 纯右值 (prvalue)(“纯 (pure)”的右值)是求值符合下列之一的表达式:
- 亡值 (xvalue)(“将亡 (expiring)”的值)是代表其资源能够被重新使用的对象或位域的泛左值;
- 左值 (lvalue)(如此称呼的历史原因是,左值可以出现于赋值表达式的左边)是非亡值的泛左值;
- 右值 (rvalue)(如此称呼的历史原因是,右值可以出现于赋值表达式的右边)是纯右值或者亡值。