operator() 的类或结构体实例。这使得该对象可以像普通函数一样被调用。[capture-list] (parameters) mutable -> return-type { function-body }| 组成部分 | 是否必填 | 说明 |
|---|---|---|
[capture-list] | 必填 | 捕捉列表:控制 Lambda 访问外部局部变量的方式。 |
(parameters) | 可选 | 参数列表:与普通函数一致,无参可省略 ()。 |
mutable | 可选 | 默认按值捕捉的变量副本是 const 的,添加此关键字后可修改副本。 |
-> return-type | 可选 | 返回值类型:通常可由编译器自动推导,复杂逻辑下可显式指定。 |
{ body } | 必填 | 函数体:实现具体逻辑。 |
[=]:隐式按值捕捉(获取当前作用域所有变量的副本)。[&]:隐式按引用捕捉。[a, &b]:明确指定变量 a 按值捕捉,变量 b 按引用捕捉。= 或 &。例如 [=, &x] 表示除 x 按引用捕捉外,其余全部按值捕捉。❓ 发散:为什么 Lambda 表达式默认按值捕捉的变量是不可修改的(需要加
mutable)?
? 解答:因为 Lambda 底层会被编译器转换为一个仿函数类。按值捕捉的变量会变成该类的成员变量。默认情况下,编译器生成的
operator()是一个const成员函数,因此无法修改类的成员变量。加上mutable关键字,本质上就是去掉了operator()的const限定符。
std::function 通过多态(虚函数)在底层实现了类型擦除,使得不同类型的可调用对象对外表现完全一致。std::function 的原型就是利用了模板特化来精准匹配函数签名 R(Args...)。std::function#include <functional> template< class R, class... Args > class function<R(Args...)>;
R 表示函数的返回值类型(Return type),Args... 表示可变参数列表类型(Argument types)。R(Args...) 被称为“函数签名”。using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;void”的任何代码逻辑,都可以被赋值给 ioservice_t 类型。ioservice_t 就是两者之间的“协议插座”或“任务规格”。void MyFunc(std::shared_ptr<Socket> &s, InetAddr &a);[](std::shared_ptr<Socket> &s, InetAddr &a){ ... };struct MyHandler { void operator()(std::shared_ptr<Socket> &s, InetAddr &a); };std::bind)。笔记:
当业务接口复杂,或需要额外参数(如数据库句柄等)时,有三种标准做法将其适配为 std::function 要求的签名:
方法一:利用 Lambda 表达式“捕获”变量(最现代、简洁)
Database db;
server.Start([&db](std::shared_ptr<Socket> &sock, InetAddr &client) {
db.Query(sock->Recv(...)); // 捕获外部 db 进行操作
});
方法二:使用 std::bind 进行参数适配
void SuperService(std::shared_ptr<Socket>& s, InetAddr& a, MyManager* mgr); MyManager manager; // 将 mgr 压入,生成只接受 2 个参数的新对象 auto service = std::bind(SuperService, std::placeholders::_1, std::placeholders::_2, &manager); server.Start(service);
方法三:封装成“业务类”(仿函数)
class ChatBusiness {
public:
void operator()(std::shared_ptr<Socket> &sock, InetAddr &client) {
Login(); SaveMsg(); // 执行内部复杂逻辑
}
private:
void Login(); void SaveMsg();
};
ChatBusiness cb;
server.Start(cb);
一句话总结:std::function 让服务器框架不再关心“做什么”,而只关心“怎么连”,实现了一套代码、百种业务的高度复用(扮演“分拣员”角色)。
std::placeholders 命名空间中(如 _1, _2),用于在绑定时“占位”,代表未来新生成的函数被调用时,由调用者动态传入的参数位置。std::bind#include <functional> template< class F, class... Args > /* 返回值类型未命名,通常用 auto 或 std::function 接收 */ bind( F&& f, Args&&... args );
功能与参数说明:f 是待包装的可调用对象;args 是要绑定的参数列表,可以是具体数值(提前固定),也可以是占位符。返回值是一个临时的仿函数对象。
int Sub(int a, int b) { return a - b; }// 新函数的参数 2 传给 a,参数 1 传给 b auto f1 = std::bind(Sub, std::placeholders::_2, std::placeholders::_1); // f1(10, 5) 实际执行 Sub(5, 10),结果为 -5
用法 2:调整个数(固定参数 / 局部应用)
// a 强制固定为 100,新函数的参数 1 传给 b auto f2 = std::bind(Sub, 100, std::placeholders::_1); // f2(5) 实际执行 Sub(100, 5),结果为 95
用法 3:绑定类成员函数(高频场景)
class Plus {
public:
int plusd(int a, int b) { return a + b; }
};
Plus pd;
auto f = std::bind(&Plus::plusd, &pd, std::placeholders::_1, std::placeholders::_2);
// f(10, 20) 等价于 pd.plusd(10, 20)
&类名::函数名。&obj)或引用,以填补成员函数隐含的 this 指针。auto 接收;跨函数传递用 std::function 包装。std::bind 默认执行值拷贝。如需传递引用,必须显式配合 std::ref 使用。std::bind 的场景都能被 Lambda 替代。Lambda 语法更直观,且编译器更容易对其进行内联优化,性能更佳。❓ 导师发散提问:为什么大部分情况下,现代 C++ 提倡用 Lambda 替代
std::bind?? 解答:首先是可读性,嵌套的
std::bind和大量的_1,_2占位符极难阅读,而 Lambda 参数流向清晰;其次是性能,std::bind内部机制复杂且依赖大量模板元编程,其生成的对象很难被编译器彻底内联优化,而 Lambda 会生成简单的局部匿名类,编译器可以轻易将其内联展开。