【C++11 新特性学习笔记:Lambda、std::function 与可调用对象】
C++11 新特性学习笔记:Lambda、std::function 与可调用对象
本文主要记录 Lambda 表达式、
std::function、成员函数包装、右值引用以及default/delete等内容,文末会与linux线程进行联动。核心思想:C++ 中有很多“可以被调用的东西”,C++11 提供了一套更加统一、方便的方式来表示和使用它们。
一、为什么 C++11 要引入 Lambda?
在 C++11 之前,如果想临时写一个“小函数”,通常需要单独定义:
bool Greater(int x, int y)
{
return x > y;
}
如果只是某个地方临时使用一次,这种写法比较麻烦。
C++11 提供了 Lambda:
auto greater = [](int x, int y)
{
return x > y;
};
之后就可以:
cout << greater(10, 5) << endl;
Lambda 的核心作用:
在需要可调用对象的地方,直接定义一个匿名的可调用对象。
二、Lambda 的基本语法
完整形式:
[capture](parameters) -> return_type
{
function_body
};
例如:
auto add = [](int a, int b) -> int
{
return a + b;
};
实际使用中返回值通常可以自动推导:
auto add = [](int a, int b)
{
return a + b;
};
最常见的写法:
[](参数)
{
函数体
}
其中:
[capture] 捕获列表
(parameters) 参数列表
-> return_type 返回值类型
{...} 函数体
三、Lambda 最重要的部分:捕获列表
Lambda 可以捕获外部变量。
3.1 值捕获
int a = 10;
auto f = [a]()
{
cout << a << endl;
};
可以理解为 Lambda 对象内部保存了一份 a 的副本。
3.2 引用捕获
int a = 10;
auto f = [&a]()
{
a++;
};
&a 表示捕获 a 的引用,因此修改 Lambda 中的 a 会影响外部变量。
3.3 常见捕获方式
[a, b] // 值捕获 a、b
[&a, &b] // 引用捕获 a、b
[=] // 默认值捕获
[&] // 默认引用捕获
[=, &a] // 默认值捕获,但 a 引用捕获
[&, a] // 默认引用捕获,但 a 值捕获
其中:
[=]
表示 Lambda 中使用到的外部局部变量默认采用值捕获;
[&]
表示默认采用引用捕获。
四、局部变量和静态变量
Lambda 中普通局部变量通常需要捕获,而静态变量和全局变量不需要捕获。
例如:
int global = 100;
void Test()
{
int local = 10;
static int sta = 20;
auto f = []
{
cout << global << endl;
cout << sta << endl;
};
}
这里 global 和 sta 都可以直接使用。
但是普通局部变量:
int local = 10;
auto f = []
{
cout << local << endl;
};
就需要通过捕获列表捕获。
五、Lambda 的本质:匿名仿函数对象
这是理解 Lambda 最重要的知识之一。
例如:
int a = 10;
auto f = [a](int x)
{
return x + a;
};
Lambda 看起来像一个函数,但它更接近一个匿名的类对象。
可以粗略理解为编译器生成了类似这样的类:
class Lambda
{
private:
int a;
public:
Lambda(int a)
: a(a)
{}
int operator()(int x)
{
return x + a;
}
};
于是:
auto f = [a](int x)
{
return x + a;
};
可以粗略理解为:
Lambda f(a);
而:
f(10);
本质上就是:
f.operator()(10);
所以:
Lambda 的本质可以理解为一个匿名的仿函数对象。
这也解释了为什么 Lambda 可以保存捕获的变量——这些数据可以理解成被保存到了对象内部。
六、mutable:修改值捕获的副本
考虑:
int a = 10;
auto f = [a]()
{
a++;
};
这不能直接修改 Lambda 内部的 a。
如果希望修改值捕获的副本:
int a = 10;
auto f = [a]() mutable
{
a++;
cout << a << endl;
};
注意:
mutable修改的是 Lambda 内部保存的那份副本,不会修改外面的a。
int a = 10;
auto f = [a]() mutable
{
a++;
};
f();
cout << a << endl; // 仍然是 10
七、引用捕获需要注意生命周期
引用捕获很方便,但必须注意生命周期。
例如:
function<int()> Test()
{
int x = 10;
return [&]()
{
return x;
};
}
这里存在问题:
Test()
↓
创建 x
↓
Lambda 引用 x
↓
Test() 返回
↓
x 被销毁
↓
Lambda 仍然存在
↓
引用悬空
因此:
不要让引用捕获的 Lambda 活得比它引用的变量更久。
这和智能指针中的生命周期问题本质上是相通的。
八、什么是可调用对象?
std::function 之前要先理解:
可调用对象(Callable)就是可以使用
()进行调用的对象。
常见的可调用对象:
普通函数
函数指针
Lambda
仿函数
成员函数
std::bind 生成的对象
例如:
int Add(int a, int b)
{
return a + b;
}
函数指针:
int (*pf)(int, int) = Add;
Lambda:
auto f = [](int a, int b)
{
return a + b;
};
仿函数:
struct Add
{
int operator()(int a, int b)
{
return a + b;
}
};
它们类型不同,但都有:
对象(参数)
这样的调用方式。
九、std::function:可调用对象的统一包装器
std::function 位于:
#include <functional>
它的核心作用:
把不同类型的可调用对象统一包装起来,并提供统一的调用方式。
例如:
function<int(int, int)> f;
可以理解为:
我要一个能够接收两个
int,最终返回一个int的可调用对象。
然后:
f(10, 20);
就可以统一调用。
十、function 可以包装哪些东西?
10.1 普通函数
int Add(int a, int b)
{
return a + b;
}
function<int(int, int)> f = Add;
cout << f(10, 20) << endl;
10.2 函数指针
int Add(int a, int b)
{
return a + b;
}
int (*pf)(int, int) = Add;
function<int(int, int)> f = pf;
cout << f(10, 20) << endl;
10.3 Lambda
function<int(int, int)> f =
[](int a, int b)
{
return a + b;
};
cout << f(10, 20) << endl;
10.4 仿函数
struct Add
{
int operator()(int a, int b)
{
return a + b;
}
};
function<int(int, int)> f = Add();
cout << f(10, 20) << endl;
因此:
普通函数
函数指针
Lambda
仿函数
↓
std::function
↓
统一调用接口
十一、Lambda 和 function 的区别
例如:
auto f1 = [](int x)
{
return x * 2;
};
这里 f1 是一个 Lambda 对象。
而:
function<int(int)> f2 = f1;
这里 f2 是一个 std::function 对象,它内部保存了 Lambda。
所以:
Lambda
↓
一个具体的匿名可调用对象
function
↓
一个可以统一包装各种可调用对象的包装器
因此二者不是同一个东西。
十二、std::function 的实际应用:逆波兰表达式
逆波兰表达式把运算符写在操作数后面。
例如:
1 2 + 3 *
对应普通表达式:
(1 + 2) * 3
可以利用栈计算:
1 2 +
↓
3
3 3 *
↓
9
使用 std::function,可以把不同运算统一包装。
完整代码
#include <iostream>
#include <stack>
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>
using namespace std;
int main()
{
// 将不同运算统一包装成 function<double(double, double)>
unordered_map<string, function<double(double, double)>> operators;
operators["+"] = [](double x, double y)
{
return x + y;
};
operators["-"] = [](double x, double y)
{
return x - y;
};
operators["*"] = [](double x, double y)
{
return x * y;
};
operators["/"] = [](double x, double y)
{
return x / y;
};
// 逆波兰表达式:1 2 + 3 *
vector<string> tokens =
{
"1", "2", "+", "3", "*"
};
stack<double> st;
for (const auto& token : tokens)
{
auto it = operators.find(token);
if (it == operators.end())
{
// 不是运算符,就是数字
st.push(stod(token));
}
else
{
// 运算符:注意先取右操作数,再取左操作数
double right = st.top();
st.pop();
double left = st.top();
st.pop();
// 调用对应的 Lambda
double result = it->second(left, right);
st.push(result);
}
}
cout << "result = " << st.top() << endl;
return 0;
}
输出:
result = 9
这里最关键的是:
unordered_map<string, function<double(double, double)>> operators;
建立了:
"+" → 加法 Lambda
"-" → 减法 Lambda
"*" → 乘法 Lambda
"/" → 除法 Lambda
遇到运算符时:
it->second(left, right);
直接调用对应操作。
这样就不用写大量:
if (token == "+")
{
...
}
else if (token == "-")
{
...
}
这就是 function 的实际价值:
把不同的操作统一成可调用对象,然后通过统一接口执行。
十三、为什么学习 Linux 线程时会突然遇到 Lambda 和 function?
如果你是从 Linux C/C++ 后端的学习路线过来的,那么这一章其实不是“额外学习一堆 C++11 语法”,而是在给后面的线程代码补基础。
最典型的就是 Linux 的 pthread_create()。
传统的 pthread 创建线程:
#include <iostream>
#include <pthread.h>
using namespace std;
void* ThreadRoutine(void* args)
{
cout << "hello thread" << endl;
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
pthread_join(tid, nullptr);
return 0;
}
这里最关键的是:
pthread_create(
&tid,
nullptr,
ThreadRoutine,
nullptr
);
pthread_create() 的第三个参数需要的是一个线程执行函数。
可以把它理解成:
创建线程
↓
告诉系统“线程启动以后执行哪个函数”
↓
ThreadRoutine
这也是为什么你在学习 Linux 线程时,经常会看到:
void* (*)(void*)
这样的函数指针类型。
十四、为什么 C++ 封装线程后,Lambda 和 function 就出现了?
如果我们只写 C 风格的 pthread:
void* ThreadRoutine(void* args)
{
// ...
}
还比较简单。
但是 C++ 更希望把“线程”和“线程要执行的任务”封装到一个类里。
例如我们可能希望这样使用:
Thread t([]()
{
cout << "hello thread" << endl;
});
t.Start();
这里就出现了一个问题:
Lambda 是一个对象,而
pthread_create()需要一个特定形式的函数指针,怎么把二者联系起来?
这正是 C++ 封装 Linux 线程时 Lambda、函数指针、std::function 经常一起出现的原因。
十五、用 function 保存“线程要执行的任务”
假设我们自己封装一个线程类:
class Thread
{
public:
Thread(function<void()> func)
: _func(func)
{}
private:
pthread_t _tid;
function<void()> _func;
};
这里:
function<void()> _func;
特别值得理解。
它的意思不是:
“这是一个函数。”
而是:
这是一个可以被调用、无参数、无返回值的任务。
例如:
_func = []()
{
cout << "hello thread" << endl;
};
也可以:
void Task()
{
cout << "hello thread" << endl;
}
_func = Task;
还可以放一个仿函数。
所以:
Lambda
普通函数
函数指针
仿函数
↓
std::function<void()>
↓
统一表示“一个任务”
这就是 std::function 在线程代码中的实际意义。
十六、pthread_create 和 C++ 类之间的矛盾
继续往下看。
假设我们写:
class Thread
{
public:
void Run()
{
cout << "hello thread" << endl;
}
};
我们可能很自然地想:
pthread_create(&_tid, nullptr, Run, this);
但是这是不行的。
为什么?
因为:
Run
是成员函数,而 pthread_create() 需要的是类似:
void* (*)(void*)
的普通函数指针。
成员函数和普通函数不是一回事。
成员函数背后隐含了一个:
this 指针
所以不能直接当成普通函数指针传进去。
十七、经典解决方案:static + this
Linux C++ 线程封装中经常看到这种写法:
class Thread
{
public:
void Start()
{
pthread_create(&_tid, nullptr, ThreadRoutine, this);
}
private:
static void* ThreadRoutine(void* args)
{
Thread* self = static_cast<Thread*>(args);
self->Run();
return nullptr;
}
void Run()
{
cout << "hello thread" << endl;
}
private:
pthread_t _tid;
};
这里是一个非常重要的设计。
创建线程时:
pthread_create(&_tid, nullptr, ThreadRoutine, this);
我们把:
this
作为第四个参数传进去。
于是:
当前 Thread 对象
↓
this
↓
pthread_create()
↓
线程启动
↓
ThreadRoutine(void* args)
↓
args 接收到 this
↓
还原成 Thread*
↓
self->Run()
这里你之前学到的:
static_cast<Thread*>(args)
就又出现了。
它的作用就是:
把
void*还原成原来的Thread*。
十八、为什么 ThreadRoutine 要写成 static?
这里也非常容易困惑。
如果写:
class Thread
{
void* ThreadRoutine(void* args);
};
那么它实际上是一个成员函数。
成员函数调用时需要一个对象,也就是隐含的:
this
但是 pthread 根本不知道你的 C++ 对象是什么。
它只知道:
void* (*)(void*)
所以这里通常使用:
static void* ThreadRoutine(void* args)
static 成员函数没有隐藏的 this 参数,因此它的函数形式可以匹配 pthread 需要的普通函数指针。
然后我们再通过:
void* args
把 this 传回来。
这是一种非常经典的:
C 接口 + C++ 对象封装
的桥接方式。
十九、把 function 和 pthread 完整串起来
现在可以进一步把线程类写成:
class Thread
{
public:
Thread(function<void()> func)
: _func(func)
{}
void Start()
{
pthread_create(&_tid, nullptr, ThreadRoutine, this);
}
void Join()
{
pthread_join(_tid, nullptr);
}
private:
static void* ThreadRoutine(void* args)
{
Thread* self = static_cast<Thread*>(args);
// 真正执行线程任务
self->_func();
return nullptr;
}
private:
pthread_t _tid;
function<void()> _func;
};
使用:
int main()
{
Thread t([]()
{
cout << "hello thread" << endl;
});
t.Start();
t.Join();
return 0;
}
现在整条链路就非常清楚了:
Lambda
↓
创建一个任务
↓
function<void()>
↓
保存任务
↓
Thread 对象
↓
pthread_create()
↓
static ThreadRoutine()
↓
通过 this 找回 Thread 对象
↓
_func()
↓
执行 Lambda
这就是为什么你在 Linux 线程代码里会突然遇到:
Lambda
function
static
this
void*
static_cast
它们并不是毫无关系的一堆知识点,而是在共同完成:
“把 C++ 的一个任务交给 Linux 线程执行”这件事。
二十、Lambda 为什么有时候可以直接传给 pthread?
这里还有一个很重要的细节。
例如:
pthread_create(
&tid,
nullptr,
[](void* args) -> void*
{
cout << "hello" << endl;
return nullptr;
},
nullptr
);
这个 Lambda 没有捕获外部变量。
这种 Lambda 可以转换成对应形式的普通函数指针,因此可以作为 pthread_create() 的第三个参数。
但是如果:
int x = 10;
auto func = [x](void* args) -> void*
{
cout << x << endl;
return nullptr;
};
这个 Lambda 捕获了 x。
此时它本质上已经是一个需要保存状态的对象:
Lambda对象
├── 保存 x
└── operator()()
它不能简单地转换成普通函数指针。
这也是为什么实际 C++ 线程封装中,经常不是直接把一个复杂 Lambda 塞进 pthread,而是:
Lambda
↓
function
↓
保存任务
↓
Thread对象
↓
pthread_create
↓
静态入口函数
↓
调用function
这样就把“Linux 线程入口”和“真正执行的 C++ 任务”分开了。
二十一、C++11 的 std::thread 又是什么?
C++11 不仅提供了 Lambda 和 std::function,还提供了:
#include <thread>
于是可以直接:
#include <iostream>
#include <thread>
using namespace std;
int main()
{
thread t([]()
{
cout << "hello thread" << endl;
});
t.join();
return 0;
}
相比 pthread:
pthread_create(...)
std::thread 更贴近 C++ 的对象模型。
它可以直接接受各种可调用对象:
普通函数
Lambda
仿函数
成员函数
例如:
void Task()
{
cout << "hello" << endl;
}
thread t1(Task);
thread t2([]()
{
cout << "hello" << endl;
});
因此可以粗略理解:
pthread
↓
Linux/POSIX 原生线程接口
↓
偏 C 风格
std::thread
↓
C++11 对线程的封装
↓
更方便地使用各种可调用对象
当然,在 Linux 后端学习中仍然非常有必要理解 pthread,因为它能帮助我们理解线程创建、线程入口、线程参数等更底层的机制。
二十二、为什么线程池特别喜欢 function<void()>?
当你继续学习线程池时,很可能看到:
queue<function<void()>> _tasks;
第一次看可能会觉得:
“队列里面为什么放函数?”
其实这里的“函数”更准确地说是:
一个个可以被执行的任务对象。
例如:
_tasks.push([]()
{
cout << "task 1" << endl;
});
_tasks.push([]()
{
cout << "task 2" << endl;
});
线程从任务队列中取任务:
function<void()> task;
task = _tasks.front();
_tasks.pop();
task();
整个过程:
任务生产者
↓
┌────────────────────┐
│ queue<function<>> │
└────────────────────┘
↓
取任务
↓
function<void()>
↓
task()
↓
工作线程执行
这就是线程池最核心的思想之一:
线程负责“执行”,任务负责“描述要执行什么”。
而 Lambda + std::function 恰好让“任务”变得非常容易表示。
例如:
function<void()> task = []()
{
cout << "计算任务" << endl;
};
甚至可以捕获参数:
int x = 10;
int y = 20;
function<void()> task = [x, y]()
{
cout << x + y << endl;
};
这时任务对象内部就保存了:
x
y
以及 operator()
所以线程拿到任务以后:
task();
就能直接执行。
二十三、从 Lambda 一路走到线程池
现在可以把你目前学习的内容完整串起来:
C++11
│
┌─────────┴─────────┐
↓ ↓
Lambda std::function
│ │
│ 统一包装任务
│ │
└─────────┬─────────┘
↓
function<void()>
↓
一个任务
↓
任务队列
↓
工作线程取任务
↓
task()
↓
执行任务
再和 Linux pthread 联系起来:
C++任务
↓
function<void()>
↓
Thread对象
↓
pthread_create()
↓
Linux线程
↓
线程入口函数
↓
调用function
↓
真正执行任务
这时候你会发现,之前课堂上那些看起来“东一个、西一个”的 C++11 特性,其实都在为后面的 Linux/C++ 编程服务。
二十四、Lambda、function 和线程:一句话理解
如果只想留下一个最核心的印象,可以记住:
Lambda 负责方便地创建任务,
std::function负责统一保存任务,而线程负责执行任务。
再进一步:
Lambda
→ 我想让线程干什么?
function<void()>
→ 把“这件事”保存下来
pthread / std::thread
→ 找一个线程去执行它
这样以后再看到:
function<void()> task;
就不要把它理解成一个复杂的 C++11 语法,而应该直接想到:
“这里保存的是一个以后可以执行的任务。”
二十六、function 包装成员函数
例如:
class Plus
{
public:
double plusd(double x, double y)
{
return x + y;
}
};
成员函数不能像普通函数一样直接调用,因为它属于某个对象。
获取成员函数地址:
&Plus::plusd
注意:
获取成员函数地址时,需要加上类名和
&。
二十七、第一种方式:使用对象指针
function<double(Plus*, double, double)> f1 = &Plus::plusd;
Plus p1;
cout << f1(&p1, 1.1, 1.11) << endl;
这里 Plus* 表示调用成员函数时需要传入一个 Plus 对象指针。
二十八、第二种方式:直接使用对象
function<double(Plus, double, double)> f2 = &Plus::plusd;
cout << f2(Plus(), 1.1, 1.11) << endl;
这里直接构造了一个临时 Plus 对象:
Plus()
所以不需要自己创建对象。
二十九、第三种方式:使用右值引用
function<double(Plus&&, double, double)> f3 = &Plus::plusd;
cout << f3(Plus(), 1.1, 1.11) << endl;
因为:
Plus()
本身就是右值,所以可以传给:
Plus&&
如果已有对象:
Plus p1;
还可以:
f3(std::move(p1), 1.1, 1.11);
注意:
std::move()本身不会真正执行移动,它主要是把一个对象转换成右值表达式,从而允许匹配右值引用。
真正的移动行为需要类型提供移动构造、移动赋值等操作。
三十、成员函数指针与 .* / ->*
成员函数指针可以写成:
double (Plus::*pf)(double, double) = &Plus::plusd;
如果有对象:
Plus p;
可以:
(p.*pf)(1.1, 2.2);
.* 表示:
通过对象 + 成员函数指针调用成员函数。
如果是对象指针:
Plus* p = new Plus;
则使用:
(p->*pf)(1.1, 2.2);
因此:
对象 + 成员函数指针
↓
.*
对象指针 + 成员函数指针
↓
->*
std::function 帮助我们把这些调用细节包装起来。
三十一、std::function 背后的思想:类型擦除
考虑:
function<int(int)> f;
它可以保存:
int func(int);
也可以保存:
struct Test
{
int operator()(int x)
{
return x * 2;
}
};
还可以保存:
[](int x)
{
return x * 2;
}
这些东西实际上都是不同的类型。
function 能够统一保存它们,背后涉及一个重要概念:
类型擦除(Type Erasure)
简单理解:
不同类型的可调用对象
↓
function
↓
隐藏具体类型
↓
统一通过 operator() 调用
目前只需要理解这个思想,不必深入源码。
三十二、default 和 delete
C++11 引入了:
= default
= delete
19.1 default
class A
{
public:
A() = default;
};
意思是:
告诉编译器使用默认生成的实现。
19.2 delete
class A
{
public:
A(const A&) = delete;
};
意思是:
明确禁止调用这个函数。
例如上面的代码就是禁止拷贝构造。
这和 unique_ptr 可以联系起来:
unique_ptr(const unique_ptr&) = delete;
所以 unique_ptr 不能拷贝,本质上就是类主动删除了拷贝构造函数。
三十三、这一部分知识之间的关系
可以把它们串成:
可调用对象
│
┌─────────────┼─────────────┐
↓ ↓ ↓
普通函数 仿函数 Lambda
│ │ │
└─────────────┼─────────────┘
↓
std::function
↓
统一包装
↓
统一调用
其中:
Lambda 是一种方便创建匿名可调用对象的语法,本质可以理解成匿名仿函数。
而:
std::function负责把不同类型的可调用对象统一包装起来。
三十四、这一部分最值得记住的内容
Lambda
Lambda = 匿名函数对象
重点:
[]
[=]
[&]
[a]
[&a]
mutable
以及:
Lambda 本质是一个重载了
operator()的匿名对象。
function
std::function = 可调用对象包装器
可以包装:
普通函数
函数指针
Lambda
仿函数
成员函数
核心价值:
统一不同类型可调用对象的调用方式。
成员函数
&Class::func
获取成员函数指针。
对象 .* 成员函数指针
对象指针 ->* 成员函数指针
右值引用
std::move(x)
主要作用:
把 x 转换成右值表达式。
它本身不等于“执行移动”。
default / delete
= default
→ 使用默认实现。
= delete
→ 禁止使用该函数。
三十五、和后面的线程联系起来
这些知识看起来比较零散:
Lambda
function
仿函数
成员函数指针
右值引用
但后面学习线程时会逐渐串起来。
例如:
thread t([]()
{
cout << "hello thread" << endl;
});
这里直接使用了 Lambda。
线程池中又经常会看到:
function<void()> task;
可以把各种不同的任务统一保存起来:
任务队列
↓
function<void()>
↓
保存各种不同的任务
↓
线程取出任务
↓
task()
这时候就会发现:
Lambda + function 并不是为了增加语法复杂度,而是为了让“任务”这种东西能够被统一表示、保存和调用。
总结
这次回来补 C++11,真正的目的其实不是把所有 C++11 新语法全部学一遍,而是解决一个非常实际的问题:
为什么学习 Linux 线程的时候,会突然遇到 Lambda、仿函数、
std::function、成员函数指针、右值引用这些东西?
现在可以把它们串起来:
Lambda
↓
快速创建一个可调用对象
仿函数
↓
理解 Lambda 的底层思想
std::function
↓
统一包装各种可调用对象
function<void()>
↓
非常适合表示“一个待执行的任务”
pthread / std::thread
↓
让线程去执行任务
任务队列
↓
保存大量 function<void()>
线程池
↓
工作线程不断取出任务并执行
因此,如果从 Linux C/C++ 后端的角度来看:
Lambda 负责方便地描述“要做什么”,
std::function负责把这个任务保存下来,线程负责把任务执行起来。
而成员函数指针、static、this、void*、static_cast 等内容,则是在解决:
如何把 C 风格的 Linux pthread 接口和 C++ 的对象模型连接起来。
最后把几个核心知识点压缩成几句话:
Lambda 是一种方便创建匿名可调用对象的语法,本质上可以理解为仿函数。
std::function是可调用对象的通用包装器,可以把普通函数、函数指针、Lambda、仿函数等统一起来。成员函数属于对象,因此使用成员函数指针时需要结合对象或对象指针进行调用。
std::move主要负责把对象转换成右值表达式,而真正的移动由移动构造/移动赋值等函数完成。在 Linux/C++ 后端中,Lambda +
std::function最重要的应用之一,就是把“任务”从线程本身中抽离出来,让线程可以统一执行各种不同的任务。
更多推荐
所有评论(0)