为何嵌套std::bind仍提前计算时间?《Effective Modern C++》实践疑问
我读了《Effective Modern C++》的Item34:优先使用lambda而非std::bind,里面提到嵌套std::bind可以延迟表达式求值,直到目标函数被调用。于是写了测试代码验证,但两次调用绑定后的setSoundB函数时,输出的时间值完全一样,看起来表达式在调用std::bind时就计算了,这是为什么?
测试代码
#include <bits/stdc++.h> #include <boost/type_index.hpp> #include <memory> using namespace std; using Time = std::chrono::steady_clock::time_point; enum class Sound { Beep, Siren, Whistle }; using Duration = std::chrono::steady_clock::duration; template <typename T> std::underlying_type_t<T> printEnum(T const value) { return static_cast<std::underlying_type_t<T>>(value); } void setAlarm(Time t, Sound s, Duration d) { cout << "time:" << t.time_since_epoch().count() << " ring" << " duraion:" << (std::chrono::duration_cast<std::chrono::seconds>(d)).count() << " sound:" << static_cast<typename std::underlying_type<Sound>::type>(s) << " sound:" << printEnum(s) << endl; } int main() { using namespace std::chrono; using namespace std::literals; using namespace std::placeholders; auto setSoundB = std::bind(setAlarm, std::bind(std::plus<>(), steady_clock::now(), 1h), _1, seconds(30)); cout << "function calc time:" << steady_clock::now().time_since_epoch().count() << endl; setSoundB(Sound::Beep); cout << "function calc time:" << steady_clock::now().time_since_epoch().count() << endl; setSoundB(Sound::Beep); return 0; }
运行结果
function calc time:1670995052139460000 time:1670998652139145000 ring duraion:30 sound:0 sound:0 function calc time:1670995052139480000 time:1670998652139145000 ring duraion:30 sound:0 sound:0
原因分析与解决方法
核心问题出在你传递给内层std::bind的steady_clock::now()——这个函数调用在创建bind对象时就被执行了,而不是延迟到setSoundB调用时。
std::bind的参数处理逻辑是:当你传递一个表达式(比如函数调用)作为绑定参数时,除非这个参数是std::placeholders占位符,或者是另一个bind返回的函数对象,否则它会立即求值,把结果存储到bind对象里。
你这里的steady_clock::now()是一个立即执行的函数调用,它的返回值(当前时间点)会被直接计算出来,然后和1h一起传给std::plus<>的bind对象。内层bind对象存储的是这个计算后的时间值,而不是“调用steady_clock::now()再加1小时”的逻辑。
修复方案
要实现延迟求值,你需要把steady_clock::now()包装成一个可调用对象,让它在每次setSoundB调用时才执行。比如用lambda代替内层的bind:
auto setSoundB = std::bind(setAlarm, []{ return steady_clock::now() + 1h; }, _1, seconds(30));
或者更符合《Effective Modern C++》Item34建议的,直接用lambda实现整个逻辑,可读性和灵活性都更好:
auto setSoundB = [](Sound s) { setAlarm(steady_clock::now() + 1h, s, 30s); };
这样每次调用setSoundB时,steady_clock::now()才会被执行,得到的时间值自然每次都不一样。
内容的提问来源于stack exchange,提问作者OverDrive

