0、背景
在查看muduo的开源代码的时候有一个代码:
cpp
// 需要一个Timestamp的参数
void setReadCallback( std::function<void(Timestamp)> cb)
void EventLoop::handleRead();
//使用bind,只需要传递this,而没有传递Timestamp,也没有什么占位符标记
wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
当我尝试使用lambda进行编写的时候:
cpp
wakeupChannel_->setReadCallback([this]() { handleRead(); });
出现参数签名不匹配的错误:
csharp
No viable conversion from '(lambda at
/home/xvxing/workspace/cpp/mygame/src/net/EventLoop.cpp:35:37)' to
'ReadEventCallback' (aka 'function<void (Timestamp)>')
[std_function.h(375, 7): ]()Candidate constructor not viable: no known conversion from '(lambda at /home/xvxing/workspace/cpp/mygame/src/net/EventLoop.cpp:35:37)' to 'nullptr_t' (aka 'std::nullptr_t') for 1st argument
[std_function.h(386, 7): ]()Candidate constructor not viable: no known conversion from '(lambda at /home/xvxing/workspace/cpp/mygame/src/net/EventLoop.cpp:35:37)' to 'const function<void (Timestamp)> &' for 1st argument
[std_function.h(404, 7): ]()Candidate constructor not viable: no known conversion from '(lambda at /home/xvxing/workspace/cpp/mygame/src/net/EventLoop.cpp:35:37)' to 'function<void (Timestamp)> &&' for 1st argument
涉及到 std::function、Lambda 表达式 strictness (严格性) 与 std::bind loose behavior**(宽松行为)**的区别。
一、问题分析
-
using ReadEventCallback = std::function<void(Timestamp)>;这意味着ReadEventCallback必须接受 一个Timestamp类型的参数。 -
Lambda 写法:
[this]() { handleRead(); }这是一个 无参 的函数对象。它接受 0 个参数。 -
冲突: 当底层(比如
Channel类)尝试调用这个回调时,它会传入一个Timestamp(例如readCallback_(receiveTime))。- Lambda 说:"我不接受任何参数"。
- 编译器报错:"无法将一个'无参调用'转换为'需要一个 Timestamp 参数的调用'"。
但是为什么bind却可以通过? 你发现下面这行代码可以编译通过:
less
wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
哪怕 EventLoop::handleRead 本身并没有参数!
这是因为 std::bind 具有"参数丢弃"的特性。
- Lambda 的行为(严格): Lambda 生成的匿名类是非常严格的。如果你声明它不接受参数
(),那么试图传给它任何参数都会导致编译错误。 - std::bind 的行为(宽松):
std::bind生成的函数对象可以接受 任意多 的参数。当它被调用时,它只会提取你在bind时指定的占位符(如std::placeholders::_1)对应的参数。多余的参数会被静默丢弃(ignored)。
场景还原:
- 底层调用:
callback(timestamp)。 - 如果是 Lambda: 编译器检查
timestamp是否能传给()------ 失败。 - 如果是 Bind: 绑定对象接收到
timestamp。但因为它内部绑定的EventLoop::handleRead不需要参数,且你没有使用_1占位符,std::bind就直接把timestamp扔掉了,然后调用this->handleRead()。
二、简单的DEMO
cpp
#include <iostream>
#include <functional>
// 1. 定义一个回调类型:它要求必须接受一个 int 参数
using Callback = std::function<void(int)>;
// 2. 模拟底层库 (Channel/TcpConnection)
// 它在调用回调时,一定会塞进去一个参数
void performAction(Callback cb) {
int eventData = 999;
cb(eventData);
}
// 3. 这个函数不需要任何参数!
void myTask() {
std::cout << "SUCCESS: myTask 被调用了 (完全无视了传入的 int)" << std::endl;
}
int main() {
// ---------------------------------------------------------
// 场景 A: 使用 std::bind (宽松模式) -> 编译通过,运行成功
// ---------------------------------------------------------
std::cout << "--- 测试 std::bind ---" << std::endl;
// bind 发现 myTask 不需要参数,它会自动把 performAction 传进来的 '999' 丢进垃圾桶
performAction(std::bind(&myTask));
// ---------------------------------------------------------
// 场景 B: 使用普通 Lambda (严格模式) -> 编译报错!
// ---------------------------------------------------------
std::cout << "\n--- 测试 Lambda (错误写法) ---" << std::endl;
// 下面这行如果解开注释,会报错:No viable conversion
// 因为 performAction 试图传 int,但 Lambda () 拒绝接收
// performAction([]() {
// myTask();
// });
// ---------------------------------------------------------
// 场景 C: 使用正确的 Lambda (手动兼容) -> 编译通过
// ---------------------------------------------------------
std::cout << "\n--- 测试 Lambda (正确写法) ---" << std::endl;
// 我们手动写上 (int), 哪怕我们在大括号里根本不用它
// 这就是显式地告诉编译器:"我知道你会传个 int 给我,我接住它,然后丢掉"
performAction([](int placeholder) {
myTask();
});
return 0;
}