从三月中开始,我逐步研究了机器人开发中的 ROS2(Jazzy)系统。与此同时,我将官网中比较重要的程和概念文章,按照自己的学习顺序翻译成了中文,并整理记录在了公众号里。在记录的过程中,我针对一些不太理解的部分进行了额外的研究和补充说明。到目前为止,我已经完成了20多篇文章的整理和撰写。如果想回顾之前的内容,可以查阅主页中 ROS2(Jazzy)相关文章。
在研究 ROS2 的过程中,我发现它大量使用了 C++11 的新特性。这让我意识到,掌握这些特性对于深入理解 ROS2 的实现原理和优化代码非常重要。因此,我萌生了撰写 C++11 系列文章的想法。
在本文之前,已经写了一篇关于C++11的右值引用和移动语义的总结:《ROS2性能狂飙:C++11移动语义'偷梁换柱'实战》
而本文则是关于C++11及之后版本的 Lambda 表达式 以及 std::function
和std::bind
的总结。
一、std::function
和 std::bind
首先来说一下std::function
和 std::bind
,它们是 C++11 引入的两个重要工具,用于处理函数对象和函数参数的灵活绑定。它们通常结合使用,可以实现回调机制、延迟执行和参数适配等功能。
1. std::function
:通用的函数包装器
std::function
统一存储任何可调用对象:函数、lambda表达式、成员函数、函数对象等。它通过模板参数声明函数签名,隐藏具体类型,提供统一的调用接口。
cpp
#include <functional>
// 声明一个接受 int 参数、返回 void 的 function 对象
std::function<void(int)> func;
std::function
支持的调用形式:
- 普通函数
- 类成员函数(需结合
std::bind
) - Lambda 表达式
- 函数对象(重载了
operator()
的类)
cpp
// 普通函数
void print(int x) { std::cout << x << std::endl; }
// Lambda 表达式
auto lambda = [](int x) { std::cout << x << std::endl; };
// 函数对象
struct Functor {
void operator()(int x) { std::cout << x << std::endl; }
};
int main() {
std::function<void(int)> f1 = print; // 绑定普通函数
std::function<void(int)> f2 = lambda; // 绑定 Lambda
std::function<void(int)> f3 = Functor(); // 绑定函数对象
f1(42); // 输出 42
f2(42); // 输出 42
f3(42); // 输出 42
}
2. std::bind
:参数绑定与适配器
- 绑定参数:将函数的部分参数固定(部分应用),生成新的可调用对象。
- 调整参数顺序 :通过占位符(
_1
,_2
, ...)重新排列参数。 - 绑定成员函数:将成员函数绑定到对象实例。
- 需要包含头文件
<functional>
并使用命名空间std::placeholders
:
cpp
using namespace std::placeholders; // 引入 _1, _2, _3...
绑定普通函数示例
cpp
void add(int a, int b) { std::cout << a + b << std::endl; }
int main() {
// 绑定第二个参数为 10,第一个参数由调用时提供
auto bound_add = std::bind(add, _1, 10);
bound_add(5); // 输出 15(5 + 10)
}
调整参数顺序
cpp
void print(int a, double b) {
std::cout << a << ", " << b << std::endl;
}
int main() {
// 将原函数的参数顺序调换
auto reversed = std::bind(print, _2, _1);
reversed(3.14, 42); // 输出 42, 3.14
}
绑定成员函数
cpp
class MyClass {
public:
void print(int x) { std::cout << x << std::endl; }
};
int main() {
MyClass obj;
// 绑定成员函数到对象实例
auto member_func = std::bind(&MyClass::print, &obj, _1);
member_func(42); // 输出 42
}
3. std::function
和 std::bind
的配合使用
可以将 std::bind
的结果存入 std::function
来实现灵活的回调机制。
示例:事件处理器
cpp
#include <functional>
#include <vector>
class Button {
public:
using Callback = std::function<void()>;
void onClick(Callback callback) {
callbacks_.push_back(callback);
}
void click() {
for (auto& cb : callbacks_) cb();
}
private:
std::vector<Callback> callbacks_;
};
class User {
public:
void respond() { std::cout << "Button clicked!" << std::endl; }
};
int main() {
Button btn;
User user;
// 绑定成员函数到 user 对象
auto callback = std::bind(&User::respond, &user);
btn.onClick(callback);
btn.click(); // 输出 "Button clicked!"
}
二、Lambda 表达式
然后来说一下 Lambda 表达式,它是现代 C++(C++11 及以后)中一个极其强大且常用的特性,它允许你在需要函数对象的地方就地定义匿名函数。
Lambda 表达式核心概念:
Lambda 表达式本质上是一个匿名的、内联的函数对象(仿函数) 。编译器会为每个 Lambda 表达式生成一个唯一的、匿名的类类型 ,这个类重载了 operator()
,而 Lambda 体就是这个 operator()
的函数体。
Lambda 表达式的基本语法:
cpp
[capture-list] (parameters) mutable(可选) constexpr(可选) noexcept(可选) -> return-type(可选) {
// lambda 体 (function body)
}
让我们逐一分解各个部分:
1. 捕获列表 [capture-list]
:
这是 Lambda 最独特和重要的部分之一。它定义了 Lambda 体内部可以访问哪些外部作用域 (定义 Lambda 的函数作用域或全局作用域)的变量,以及如何访问它们(值捕获、引用捕获等)。
捕获的方式有:
[]
:空捕获列表。Lambda 体内不能访问任何外部作用域的变量(只能访问全局变量、静态变量或 Lambda 自己的参数)。[var1, var2, ...]
:值捕获 。Lambda 创建时,将这些外部变量var1
,var2
等的拷贝 捕获进来。Lambda 体内部修改这些拷贝不影响外部原始变量。注意:被捕获的变量必须是可拷贝的。[&var1, &var2, ...]
:引用捕获 。Lambda 捕获的是这些外部变量var1
,var2
等的引用 。Lambda 体内部修改这些引用会直接影响外部原始变量。使用引用捕获要极其小心生命周期,也就是说Lambda 执行时,被引用的对象必须仍然存在,否则会导致悬空引用(Dangling Reference),这是严重错误。[=]
:隐式值捕获 。Lambda 体内使用到的所有外部变量都按值捕获。编译器自动推断需要捕获哪些变量。[&]
:隐式引用捕获 。Lambda 体内使用到的所有外部变量都按引用捕获。编译器自动推断需要捕获哪些变量。[this]
:捕获当前对象指针this
。允许 Lambda 访问其定义所在类的成员变量和成员函数(通过this->member
或直接写member
)。同样要注意生命周期(Lambda 执行时this
指向的对象必须有效)。[*this]
(C++17):捕获当前对象的副本 。通过值捕获方式捕获整个当前对象,此时需要类可拷贝。这可以避免this
指针悬空引用(Dangling Reference)的问题,但可能会带来拷贝开销。- 混合捕获: 可以组合使用,例如:
[=, &var1]
:除var1
按引用捕获外,其他变量按值捕获。[&, var2]
:除var2
按值捕获外,其他变量按引用捕获。[var1, &var2, this]
:显式指定各个变量的捕获方式。
- 初始化捕获 (C++11 起,但更常用于 C++14 模式):
[name = expression]
: 将expression
的结果移动(如果不可移动,则为拷贝)捕获到名为name
的变量中。常用于移动捕获(避免拷贝开销)或为捕获的变量重命名。以下是移动捕获的主要方式。[ptr = std::move(unique_ptr_var)]
: 移动捕获一个unique_ptr
。[data = large_vector_var]
: 值捕获(拷贝)一个大的 vector。[alias = some_complex_expression]
: 计算表达式并捕获结果,简化 Lambda 体。[&name = expression]
:expression
必须是一个左值,name
将是该左值的引用。用于捕获一个表达式产生的左值的引用,这个方式较少用到。
2. 参数列表 (parameters)
:
与普通函数的参数列表完全相同。定义 Lambda 被调用时需要传入的参数。
- 参数列表可以是空
()
。 - 可以指定类型
(int x, const std::string& s)
。 - 可以使用
auto
作为参数类型,使 Lambda 成为模板函数对象。泛型 Lambda (C++14):
cpp
`[](auto x, auto y) { return x + y; }` // 可以接受任何支持 `+` 操作的类型
- 可以对
auto
参数应用概念约束。 概念约束 (C++20):
cpp
[]<std::integral T>(T a, T b) { return a + b; } // 只接受整数类型
3. mutable
关键字 (可选):
默认情况下,Lambda 的 operator()
是一个 const
成员函数 。这意味着对于值捕获的变量,你在 Lambda 体内部不能修改它们的值。
指定 mutable
后,Lambda 的 operator()
将不再是 const
的 。这使得你可以修改值捕获 的变量,当然,这些修改只影响 Lambda 对象内部的拷贝,不影响外部原始变量。指定 mutable
并不影响引用捕获的变量,因为你本来就可以通过引用修改外部变量。
注意: mutable
放在参数列表之后,返回类型之前。
4. constexpr
关键字 (可选, C++17 起支持):
显式指定该 Lambda 表达式可以在常量表达式中使用(编译时求值)。如果 Lambda 满足 constexpr
函数的要求,编译器会自动推断,但显式写出可以确保这个特性生效并增强可读性。
5. noexcept
关键字 (可选):
指定 Lambda 的 operator()
是否抛出异常。用法与普通函数的 noexcept
说明符相同。
6. 返回类型 -> return-type
(可选):
这部分用于指定 Lambda 的返回类型。
如果省略,编译器会根据 Lambda 体内的 return
语句自动推导返回类型。
如果 Lambda 体包含多个 return
语句且它们推导出的类型不一致,或者没有 return
语句(返回 void
),或者 Lambda 体复杂编译器难以推导,则必须显式指定返回类型。
7. Lambda 体 { ... }
:
函数的具体实现代码块,它可以包含 return
语句,可以访问:
- 捕获列表捕获的变量(按指定方式:值 或者 引用)
- 参数列表传入的参数
- 局部声明的变量
- 全局/静态变量(通常来说不推荐直接访问,这会破坏封装)
Lambda 表达式的类型与存储:
1. 唯一匿名类型:
每个 Lambda 表达式在编译时都会生成一个唯一的、匿名的类类型 。即使两个 Lambda 看起来完全一样(相同的捕获、参数、函数体),它们也是不同的类型 。 而auto
是声明 Lambda 对象最常用的方式:
cpp
auto lambda = [](int x) { return x * 2; };
2. std::function
:
当你需要存储不同类型的 Lambda 对象,或者需要将 Lambda 作为特定签名的回调传递时(例如,函数参数要求是 std::function<void(int)>
),可以使用 std::function
包装器。
cpp
std::function<int(int)> funcPtr = lambda; // 存储上面定义的 lambda
funcPtr(5); // 调用,输出 10
std::function<void()> callback = []() { std::cout << "Callback called!\n"; };
registerCallback(callback); // 假设有一个注册回调的函数
需要注意的是使用 std::function
会带来轻微的性能开销(类型擦除、潜在的动态内存分配),在性能关键路径上应谨慎使用。
建议优先直接使用 auto
或模板参数接收 Lambda。
Lambda 的核心优势与应用场景:
1. 就地定义,代码清晰:
将逻辑直接写在需要它的地方(如 STL 算法的调用点),避免跳转到外部函数定义,大大提高代码可读性和可维护性。
2. 强大的闭包(Closure):
通过捕获列表,Lambda 可以"记住"并访问其定义环境中的状态(变量),这是函数指针无法做到的。这是实现回调、事件处理、定制化行为的基石。
3. 与 STL 算法完美契合:
STL 算法(如 std::for_each
, std::find_if
, std::sort
, std::transform
, std::accumulate
等)大量接收谓词(Predicates)或函数对象作为参数。
Lambda 是在调用这些算法时定制行为的首选方式。
cpp
std::vector<int> numbers = {1, 4, 2, 8, 5};
// 使用 Lambda 查找大于 3 的第一个数
auto it = std::find_if(numbers.begin(), numbers.end(), [](int n) { return n > 3; });
// 使用 Lambda 对 vector 排序(降序)
std::sort(numbers.begin(), numbers.end(), [](int a, int b) { return a > b; });
// 使用 Lambda 计算平方和 (C++17 有 std::transform_reduce)
int sum_of_squares = std::accumulate(numbers.begin(), numbers.end(), 0,
[](int acc, int n) { return acc + n * n; });
4. 回调函数(Callbacks):
在GUI 编程(按钮点击事件)、异步操作(网络请求完成、定时器到期)、线程池任务提交等场景中,Lambda 是定义回调逻辑的自然方式,能方便地捕获上下文状态。
cpp
// 假设一个异步下载函数 downloadAsync(url, onCompleteCallback)
std::string filename = "data.txt";
downloadAsync("http://example.com/data",
[filename](const std::string& downloadedData) { // 捕获 filename (值捕获)
std::ofstream out(filename);
out << downloadedData;
});
5. 创建线程(std::thread
):
向线程传递任务函数及其所需的参数状态。
cpp
int sharedValue = 42;
std::thread worker([&sharedValue]() { // 小心!引用捕获 sharedValue
// ... 操作 sharedValue ...
});
worker.join();
6. 延迟计算:
可以将计算逻辑封装在 Lambda 中,在需要时才调用。
7. 实现小型、一次性的函数对象:
避免为只使用一次的小逻辑单独定义命名类或函数。
8. 泛型编程(C++14+):
泛型 Lambda 可以像模板函数一样工作,处理多种类型。
Lambda 表达式的注意事项:
1. 生命周期与悬空引用(Dangling Reference):
这是使用 Lambda(尤其是引用捕获 [&]
或捕获指针)时最大的陷阱。
如果 Lambda 捕获了一个局部变量的引用或指针,并且该 Lambda 的生命周期(例如,它被存储在某个地方稍后调用)超过了 被捕获变量所在的作用域,那么当 Lambda 被调用时,它所引用的变量或对象已经销毁 了。访问这些引用或指针会导致未定义行为(通常是崩溃)。
解决方案:
- 优先值捕获
[=]
或显式值捕获[var]
: 以确保 Lambda 拥有自己的拷贝。注意拷贝大对象的开销。 - 使用移动捕获
[var = std::move(var)]
(C++14): 对于只移动类型(如std::unique_ptr
)或避免大对象拷贝。 - 使用智能指针: 如果必须共享所有权或延长生命周期,捕获
std::shared_ptr
(值捕获)或std::weak_ptr
(需要检查有效性)。 - 明确所有权和生命周期: 确保 Lambda 的执行时机与被捕获对象的生命周期严格匹配。避免将捕获了局部变量引用的 Lambda 传递到可能长期存在的对象(如全局变量、另一个线程)中。
2. 默认捕获的风险:
谨慎使用隐式捕获 [=]
和 [&]
。
[&]
:可能导致意外的悬空引用(Dangling Reference)(捕获了你没意识到需要捕获的变量)。[=]
:C++11/14 中,[=]
实际上是通过值捕获this
指针(如果使用了类成员),而不是捕获成员变量本身。在成员函数内的[=]
Lambda 中修改成员变量(通过this->member
)是允许的,这是因为this
指针是值捕获的常量指针,但指向的对象不是常量,这可能造成误解。而在 C++17 中,则引入了[*this]
来真正捕获对象副本。最佳实践是显式列出需要捕获的变量或this
。
3. mutable
的语义:
需要记住 mutable
只允许你修改 Lambda 内部 值捕获变量的拷贝 ,不影响外部原始变量。如果需要修改外部变量,应使用引用捕获 [&var]
。
4. 性能差异:
- Lambda 本身通常非常高效,编译器很容易内联优化。
- 需要注意捕获大对象(值捕获)或使用
std::function
会有拷贝或间接调用开销。
5. 可读性与复杂性:
如果 Lambda 的逻辑变得非常长或复杂,考虑将其重构为一个命名函数或函数对象,以保持 Lambda 的简洁。
总之,C++ Lambda 表达式是提升代码表达力、简洁性和封装性的革命性特性。它通过强大的捕获机制(闭包)和就地定义的能力,完美解决了传统函数指针和具名函数对象在灵活性和便捷性上的不足,尤其在与 STL 算法、回调机制结合时展现出巨大威力。理解其语法(特别是捕获列表的各种方式)、生命周期管理(避免悬空引用(Dangling Reference))以及适用场景(优先于 std::bind
),是编写现代、高效、易维护 C++ 代码的关键技能。务必牢记生命周期陷阱,谨慎使用默认捕获和引用捕获。
三、std::bind
与 Lambda 的对比
在 C++11 及以后的版本中,std::bind
和 Lambda 表达式都是创建可调用对象(函数对象)的强大工具,用于实现函数适配、回调、延迟调用等。它们各有优势,但 现代 C++(C++11 之后)强烈倾向于优先使用 Lambda 表达式。以下是详细的对比总结:
一)Lambda 表达式的优势与优先使用场景
1. 语法清晰、意图明确:
- 代码在调用点附近定义: Lambda 通常直接写在需要可调用对象的地方,逻辑集中,上下文清晰。
- 直观的捕获列表:
[=]
,[&]
,[var1, &var2]
等清晰地表明了捕获哪些外部变量以及是值捕获还是引用捕获。 - 函数体可见: Lambda 的函数体直接写在定义处,逻辑一目了然。
std::bind
需要跳转到原始函数定义才能理解完整行为。 - 示例:
cpp
std::vector<int> vec = {1, 2, 3, 4};
int threshold = 2;
// Lambda: 意图清晰 (找大于 threshold 的数)
auto it_lambda = std::find_if(vec.begin(), vec.end(), [threshold](int x) { return x > threshold; });
2. 更强的捕获能力:
- 任意变量捕获: 可以按值 (
[x]
)、按引用 ([&x]
)、移动捕获 ([x = std::move(x)]
C++14+) 或初始化捕获 ([capture = expr]
C++14+) 任何需要的变量。 - 捕获成员变量: 在成员函数内部定义的 Lambda 可以直接捕获
this
([this]
,[*this]
C++17+) 来访问成员变量和成员函数。 std::bind
限制:bind
只能通过占位符_1, _2, ...
绑定调用时的参数,或者通过值拷贝绑定创建bind
表达式时提供的具体参数。它无法直接引用捕获成员变量(需要绑定this
指针和成员函数指针,语法笨拙),也无法进行移动捕获或初始化捕获。- 示例 (成员函数访问):
cpp
class Widget {
int value;
public:
void process() {
// Lambda: 直接捕获 this 访问 value
auto lambda = [this]() { std::cout << value << '\n'; };
// std::bind: 需要显式绑定 this 和成员函数指针,非常繁琐且易错
auto binder = std::bind(&Widget::printValue, this); // 假设有 printValue 成员函数
}
};
3. 更好的内联优化:
Lambda 表达式通常是简单的匿名结构体(重载了 operator()
),编译器更容易将其内联优化。
std::bind
通常返回一个编译器定义的复杂类型,其内部可能包含多个函数指针和绑定值,编译器对其进行内联优化的难度通常更大。这可能导致轻微的性能差异。虽然这通常不显著,但在极端性能敏感的代码中可能就变得很重要了。
4. 支持模板参数和 auto
参数 (C++14+):
Lambda 可以使用 auto
参数 ([](auto x, auto y) { ... }
) 创建泛型 Lambda,像模板函数一样工作。
std::bind
无法做到: bind
绑定的函数必须是具体类型。如果需要绑定模板函数或处理不同类型参数,必须预先实例化模板函数,限制了灵活性。
示例 (泛型比较):
cpp
// Lambda (C++14+): 可以比较任意可比较类型
auto compare = [](const auto& a, const auto& b) { return a < b; };
std::sort(vec.begin(), vec.end(), compare);
// std::bind: 无法直接绑定 std::less<void> 或类似模板的通用比较,需要指定具体类型实例 (如 std::less<int>())
5. 更自然的递归支持:
虽然 Lambda 默认没有名字,但可以通过赋值给 std::function
或使用 auto
和 y_combinator
技巧来实现递归。
而std::bind
实现递归比较别扭,通常需要结合 std::function
。
6. 更简单的重载函数处理:
如果目标函数有重载版本,使用 Lambda 调用非常自然(根据参数类型推断)。
而std::bind
需要显式指定要绑定哪个重载版本(通常需要强制转型),语法复杂且容易出错。
cpp
void func(int);
void func(double);
// Lambda: 简单自然
auto lambda = [](auto arg) { func(arg); }; // C++14 auto 参数
// std::bind: 需要指定类型
auto binder = std::bind(static_cast<void(*)(int)>(&func), std::placeholders::_1); // 绑定 int 版本
二)std::bind
的(少数)优势与适用场景
1. 参数重排序、忽略或设置默认值:
这是 std::bind
最核心且仍然有价值的用途。
Lambda 需要显式地在函数体内部写出参数传递的顺序和默认值逻辑。 bind
通过占位符 _1, _2, ..., _N
可以任意改变原始函数参数的顺序、固定某些参数的值(忽略它们,设为默认值)。
示例:
cpp
void func(int a, double b, const std::string& c);
// 用 bind 将参数顺序改为 (c, a, b), 并固定 c 为 "hello"
auto bound = std::bind(func, std::placeholders::_2, std::placeholders::_3, "hello"); // bound(c, a, b) -> func(a, b, "hello")
// 用 Lambda 实现相同效果: 需要在函数体内调整参数顺序并设置 c
auto lambda = [](int a, double b, const std::string& c) { func(a, b, "hello"); }; // 无法改变调用者传入参数顺序要求
// 或者(更接近 bind 效果):
auto lambda2 = [](const std::string& c, int a, double b) { func(a, b, "hello"); }; // 改变了调用签名
当需要显著改变调用接口(特别是参数顺序)来适配现有 API 时,bind
的占位符语法可能比在 Lambda 函数体内手动调整更简洁。
但要注意,这种场景在现代 C++ 中相对较少,且 Lambda 的可读性通常更好。
2. 绑定到成员函数指针和对象指针/引用 :
bind
的语法 std::bind(&Class::Member, obj_ptr, _1, _2)
或 std::bind(&Class::Member, std::ref(obj), _1, _2)
是绑定成员函数的标准方式之一。
不过,Lambda 通常是更好的选择: [obj_ptr]() { obj_ptr->member(); }
或 [&obj]() { obj.member(); }
更加直观和安全(特别是涉及对象生命周期管理时)。
bind
的方式在涉及智能指针(如 std::shared_ptr
)时可能有用,但 Lambda 同样能很好地处理(通过值捕获智能指针)。
3. 多态函数对象 (非常罕见):
std::bind
可以绑定任意可调用对象(函数、函数指针、成员函数指针、已有函数对象)。
Lambda 本身也是函数对象。如果需要将 不同类型 的函数对象统一绑定成 相同签名 的可调用对象,并且这些类型没有公共基类(除了 std::function
),可能使用bind
是更好的一种方式(通过 bind
包装后赋值给 std::function
)。但通常直接使用 std::function
或泛型编程是更优解。
三)总结与建议
优先使用 Lambda 表达式:
- 在绝大多数情况下,Lambda 是首选。 它的语法更现代、清晰、直观,定义在调用点附近提高可读性和可维护性。
- 捕获机制更强大、灵活(值、引用、移动、初始化捕获)。
- 编译器更容易优化。
- 支持泛型(C++14+),处理重载函数更自然。
- 更容易访问成员变量和成员函数(通过
this
捕获)。 - C++ 核心指南 (C++ Core Guidelines) 明确推荐优先使用 Lambda 而不是
std::bind
。
少数考虑 std::bind
的场景 :
- 需要显著改变函数参数顺序、忽略某些参数或为它们设置固定默认值,并且使用占位符语法比在 Lambda 函数体内手动调整参数更清晰简洁时。 这是
bind
在现代 C++ 中 最主要的 合理使用场景。 - 处理一些非常特殊或遗留的接口适配需求(相对罕见)。
- 即使在这些场景下,也要权衡 Lambda 的可读性优势。如果参数重排序逻辑复杂到 Lambda 函数体变得混乱,
bind
的占位符可能更简洁;但简单的重排序或默认值设置,在 Lambda 函数体内调整通常也很直观。
避免 std::bind
的场景:
- 需要移动捕获 (C++14+ Lambda 支持)。
- 需要捕获成员变量直接访问 (Lambda +
this
更好)。 - 需要泛型行为 (C++14+ 泛型 Lambda)。
- 目标函数有重载版本 (Lambda 类型推断更自然)。
- 追求极致性能(可能的内联优势)。
- 需要简单自然的递归(虽然两者都不完美,但 Lambda 结合
std::function
或 Y combinator 相对直接)。
总之,结论就是: 拥抱 Lambda。 它是现代 C++ 中创建函数对象的主力军,更清晰、更强大、更灵活。只在非常特定的参数操作需求(重排序、忽略、设默认值)且 Lambda 实现方式不够简洁时,才考虑使用 std::bind
,并充分意识到其可读性和灵活性上的劣势。
随着 C++ 标准的演进(如 C++14 泛型 Lambda, C++17 std::invoke
等),std::bind
的应用场景正在持续缩小。
作者:小芝,一个干了二十多年的C++开发。开发过桌面软件,干过古早功能手机游戏开发,也弄过客户端APP。现在对AI开发和机器人开发有兴趣,同时也在了解产品相关知识。若喜欢本文,欢迎点赞、在看、留言交流。