文章目录
- C++11
- 1. 模版的可变参数
- 1.1 模版参数包的使用
- 2. lambda表达式
- 2.1 Lambda表达式语法
- 捕获列表说明
- 2.2 lambda的底层
- 3. 包装器
- 3.1 function包装器
- 3.2 bind
- 4. 线程库
- 4.1 thread类
- 4.2 mutex类
- 4.3 atomic类
- 4.4 condition_variable类
C++11
1. 模版的可变参数
C++11支持模版的可变参数,可变模版参数比较抽象晦涩,我们只探讨其中基础。
template <class ...Args> // 模版参数包
void ShowList(Args... args) // 函数参数包
{}
...
表明是可变模版参数,称为参数包,可以有
[
0
,
N
]
[0,N]
[0,N] 个模版参数。可变参数的模版函数,同样是根据调用情况,实例化出多份。
// 展示参数包个数
cout << sizeof...(Args) << endl;
cout << sizeof...(args) << endl;
1.1 模版参数包的使用
void showlist()
{
cout << endl;
}
template<class T, class... Args>
void show_list(const T& val, Args... args)
{
cout << val << " "; // 使用第一个参数
showlist(args...); // 向下递归传递参数包
}
int main()
{
showlist();
showlist('1');
showlist('1', 2);
showlist('1', 2, "string");
return 0;
}
参数包可以递归解析。
- 首先无参调用可直接调用无参版本。
- 其次有参调用的第一个参数会被
val
获取,之后的参数会被参数包获取。 - 使用完第一个参数后,可以传参数包下去递归调用。
打印剩余的参数:
void showlist()
{
cout << endl;
}
template<class T, class...Args>
void showlist (const T& val, Args... args)
{
cout << __FUNCTION__ <<"-->" << sizeof...(args)<<endl;
//cout << val << " ";
showlist(args...);
//cout << sizeof...(args) << endl;//计算大小
//如何解析出可变参数包呢?
//不能这么玩,语法不支持
//for (int i = 0; i < sizeof...(args); i++)
//{
// cout << args[i] << " ";
//}
}
int main()
{
showlist('x', 1,2,"string");
return 0;
}
线程库就是使用可变模版参数,支持传递任意个参数。
2. lambda表达式
2.1 Lambda表达式语法
[capture-list](parameters) mutable -> return-type { statement }
语法组成 | 解释 | 是否省略 |
---|---|---|
[capture_list] | 捕获列表,捕捉当前作用域中的变量。分为传值捕捉和引用捕捉 | 不可省略 |
(param_list) | 参数列表,形参默认具有const 属性,可加mutable 去除常属性 | 可省略 |
-> ret_type | 指明返回类型 | 可省略自动推导 |
{} | 函数体内容 | 不可省略 |
各部分说明:
- capture-list: 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据来。判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
- (parameters): 参数列表与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
- mutable: 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
- -> return-type: 返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回0值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- { statement }: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:0;该lambda函数不能做任何事情。
看个样例代码:
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10);
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调 用,如果想要直接调用,可借助auto将其赋值给一个变量。
捕获列表说明
[captrue_list]
捕获列表,用来捕捉当前作用域前和全局的变量。[]
不可省略。
- 分为传值捕捉和引用捕捉,引用捕捉
[&a, &b]
。 [&]
表示全引用捕捉,[=]
表示全传值捕捉。捕捉所有能捕捉的变量。[&a, =]
表示混合捕捉,引用捕捉a
变量,其他变量传值捕捉。但不可重复捕捉。- 捕捉列表和参数列表的变量默认用
const
修饰,可加mutable
解除修饰。
auto func1 = [a, b] () {}; // 传值捕捉
auto func2 = [&a, &b] () {}; // 引用捕捉
auto func3 = [=] () {}; // 全传值捕捉
auto func4 = [&] () {}; // 全引用捕捉
// 混合捕捉
[&a, &b, =](){}; // 引用捕捉a和b变量,其他变量传值捕捉
[=, a](){}; // 重复传值捕捉a,编译报错
注意:
-
父作用域指包含lambda函数的语句块
-
语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
//Lambda表达式捕捉列表的示例
auto lambda1 = [=, &b]() {
std::cout << "Inside lambda1: a = " << a << ", b = " << b << std::endl;
// 可以访问变量a的值,但只能以值传递的方式访问,而变量b可以以引用传递的方式访问
};
- 捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
//Lambda表达式捕捉列表中不允许变量重复传递的示例
/// 以下代码将导致编译错误,因为变量a已经在捕捉列表中以值传递的方式捕捉了
// auto lambda2 = [=, a]() {}; // 编译错误:重复的捕捉变量'a'
// auto lambda3 = [=, &]() {}; //编译错误:传值和引用不可以同时存在
-
在块作用域以外的lambda函数捕捉列表必须为空。
// 在块作用域以外的lambda函数捕捉列表必须为空的示例 int c = 20; auto lambda3 = [=]() { // 在此lambda函数中只能访问到变量a和b,无法访问外部的变量c std::cout << "Inside lambda3: a = " << a << ", b = " << b << std::endl; };
-
lambda表达式之间不能相互赋值,即使看起来类型相同
// lambda表达式之间不能相互赋值的示例 // auto lambda4 = lambda3; // 编译错误:无法从lambda函数'lambda3'初始化lambda函数'lambda4'
2.2 lambda的底层
lambda表达式不能相互赋值,即使看起来类型相同。
auto lamdba = []() {};
cout << sizeof(lamdba) << endl; // 1
cout << typeid(lamdba).name() << endl; // class `int __cdecl main(void)'::`2'::<lambda_1>
// class <lambda_fcbffd5ae4b5ac20353abe92769a204f>
lambda表达式最后会被编译器处理成仿函数,所以lambda是个空类,大小为1。类名不同编译器实现不同,但能保证每个lambda表达式类名不同。
看看仿函数和lambda表达式的底层:
3. 包装器
包装器用来包装具有相同特征用途的多个可调用对象,便于以统一的形式调用它们。
3.1 function包装器
function包装器也叫做适配器,C++中的function本质是一个类模版。定义如下:
#include <functional>
template <class RetType, class... ArgsType> /* 声明返回类型和参数类型 */
class function<Ret(Args...)>;
// 普通函数
int func(int a, int b) { return a + b; }
// 仿函数
struct functor {
int operator()(int x, int y) { return x + y; }
};
// 非静态成员函数
struct Plus {
int plus(int a, int b) { return a + b; }
};
// 静态成员函数
struct Sub {
static int sub(int a, int b) { return a - b; }
};
std::function<int(int, int)> f1 = f;
std::function<int(int, int)> f2 = Functor();
std::function<int(Plus&, int, int)> f3 = &Plus::plus;
std::function<int(int, int)> f4 = Sub::sub;
封装成员函数时需要注意的点有:指定类域、对象参数、加取地址符。
struct Plus {
Plus(int i) {}
int plus(int a, int b) { return a + b; }
};
int main()
{
function<int(Plus, int, int)> f1 = &Plus::plus;
f1(Plus(1), 1, 2);
function<int(Plus&, int, int)> f2 = &Plus::plus;
Plus p(1);
f2(p, 1, 2);
function<int(Plus*, int, int)> f3 = &Plus::plus;
f3(&p, 1, 2);
function<int(Plus&&, int, int)> f4 = &Plus::plus;
f4(Plus(3), 1, 2);
return 0;
}
3.2 bind
bind函数也是一个函数包装器,本质是一个函数模版。生成一个新的可调用对象,来调整一个可调用对象的参数列表。
// without return
template <class Func, class... Args>
bind(Func&& fn, Args&&... args);
// with return type
template <class Ret, class Func, class... Args>
bind(Func&& fn, Args&&... args);
class suber
{
public:
suber(int rt) : _rt(rt)
{}
int sub(int a, int b) { return (a - b) * _rt; }
private:
int _rt;
};
// 通过bind调整参数顺序
function<int(int, int)> f1 = bind(suber, placeholders::_1, placeholders::_2);
function<int(int, int)> f2 = bind(suber, placeholders::_2, placeholders::_1);
cout << f1(2, 1) << endl;
cout << f2(1, 2) << endl;
// 通过bind调整参数个数
function<int(suber, int, int)> f3 = &Sub::sub;
function<int(int, int)> f4 = bind(&Sub::sub, Sub(3), placeholders::_1, placeholders::_2);
cout << f3(Sub(1), 2, 1) << endl;
cout << f4(2, 1) << endl;
4. 线程库
C++11提供了跨平台的具有面向对象特性的线程库,线程相关的系统知识在此不作赘述,直接讨论线程库的使用。
4.1 thread类
构造函数 | 解释 |
---|---|
thread() noexcept | 创建thread对象,不执行任何操作 |
thread(Fn&& fn, Args&&... args) | 传入调用对象和参数列表 |
thread(const thread&) = delete | 线程对象不可拷贝 |
thread(thread&& th) | 线程对象支持移动 |
成员函数 | 解释 |
void join() | 等待线程 |
void detach() | 分离线程 |
关于当前线程的一些操作被放到this_thread
类中:
this_thread 成员函数 | 解释 |
---|---|
thread::id get_id () noexcept | 返回线程ID |
void sleep_for (const chrono::duration<Rep,Period>& rel_time) | 设置休眠时间 |
vector<thread> thds(N); // 线程池
atomic<int> x = 0;
for (auto& td : thds) {
td = thread([&x, M](int i = 0) {
while (i++ < M) {
cout << this_thread::get_id() << "->" << x << endl; // get_id()
this_thread::sleep_for(std::chrono::seconds(1)); // sleep_for()
x++;
}
}
);
}
for (auto& td : thds) {
td.join();
}
4.2 mutex类
mutex类封装系统中的互斥锁,具体接口如下:
mutex | 解释 |
---|---|
mutex() noexcept | 创建互斥锁 |
mutex (const mutex&) = delete | 禁止拷贝锁 |
void lock() | 加锁 |
void unlock() | 解锁 |
lock_guard | 解释 |
explicit lock_guard (mutex_type& m) | 构造函数 |
lock_guard (const lock_guard&) = delete | 不支持拷贝 |
unique_lock | 解释 |
explicit unique_lock (mutex_type& m) | 构造函数 |
unique_lock (const unique_lock&) = delete | 不支持拷贝 |
void lock() | 加锁 |
void unlock() | 解锁 |
捕获异常并解锁释放资源是不够友好的,因此异常时资源的处理,交给RAII解决。RAII即资源获取就是初始化,是一种管理资源的用法。
本质是将资源封装成类,自动调用构造和析构。以达到资源获取自动初始化,出作用域自动释放的效果。
利用 RAII 封装的成“智能锁”,我们称之为锁守卫lock_guard
。
4.3 atomic类
保证自增减的原子性,可以使用原子操作。atomic类封装系统原子操作,具体接口如下:
template <class T> struct atomic;
T fetch_add (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; // +=
T fetch_sub (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; // -=
T fetch_and (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; // &=
T fetch_or (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; // |=
T fetch_xor (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; // ^=
T operator++() volatile noexcept; // ++
T operator--() volatile noexcept; // --
无锁算法CAS
Linux原子操作系统调用
4.4 condition_variable类
条件变量是线程同步的一种机制,主要包括两个动作:等待条件变量挂起,条件变量成立运行。
condition_variable | 解释 |
---|---|
condition_variable() | 构造条件变量 |
condition_variable (const condition_variable&) = delete | 禁止拷贝条件变量 |
void wait (unique_lock<mutex>& lck) | 直接等待 |
void wait (unique_lock<mutex>& lck, Predicate pred) | 指定条件下等待 |
void notify_one() noexcept | 唤醒单个线程 |
void notify_all() noexcept | 唤醒多个线程 |
// wait的实现
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred)
{
while (!pred()) /* pred()为假,进入等待 */
wait(lck);
}
tex>& lck) | 直接等待 | |
void wait (unique_lock& lck, Predicate pred) | 指定条件下等待 | |
void notify_one() noexcept | 唤醒单个线程 | |
void notify_all() noexcept` | 唤醒多个线程 |
// wait的实现
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred)
{
while (!pred()) /* pred()为假,进入等待 */
wait(lck);
}
模版参数pred
是个可调用对象,其返回值代表线程是否进入临界区的条件。条件为真停止等待,条件为假进入等待。