Effective Modern C++ 条款34:优先考虑lambda而非std::bind
在C++11及更高版本中,lambda表达式几乎总是比std::bind更好的选择。本文将详细探讨为何lambda优于std::bind,并通过具体示例展示两者的差异。
std::bind的历史背景
C++11中的std::bind是C++98的std::bind1st和std::bind2nd的后续,但在2005年已经非正式成为了标准库的一部分。那时标准化委员采用了TR1的文档,其中包含了bind的规范(在TR1中,bind位于std::tr1::bind命名空间)。
可读性对比
优先选择lambda而非std::bind的最重要原因是lambda更易读。考虑以下设置警报器的函数:
cpp
using Time = std::chrono::steady_clock::time_point;
enum class Sound { Beep, Siren, Whistle };
using Duration = std::chrono::steady_clock::duration;
void setAlarm(Time t, Sound s, Duration d);
使用lambda实现一小时后响30秒的警报器:
cpp
auto setSoundL = [](Sound s) {
using namespace std::chrono;
setAlarm(steady_clock::now() + hours(1), s, seconds(30));
};
而使用std::bind的等效实现:
cpp
using namespace std::chrono;
using namespace std::placeholders;
auto setSoundB = std::bind(setAlarm,
steady_clock::now() + 1h, // 这里有错误
_1,
30s);
第一个问题在于std::bind会在绑定时计算时间,而非调用时。正确的实现需要嵌套bind调用:
cpp
auto setSoundB = std::bind(setAlarm,
std::bind(std::plus<>(),
std::bind(steady_clock::now),
1h),
_1,
30s);
显然,lambda版本更简洁明了。
性能考虑
lambda通常能生成更高效的代码。在lambda中,函数调用可以被编译器内联,而std::bind通过函数指针调用,通常无法内联。
重载处理
当函数存在重载时,std::bind会遇到问题:
cpp
enum class Volume { Normal, Loud, LoudPlusPlus };
void setAlarm(Time t, Sound s, Duration d, Volume v); // 重载版本
auto setSoundB = std::bind(setAlarm, // 错误!哪个setAlarm?
std::bind(std::plus<>(),
steady_clock::now(),
1h),
_1,
30s);
必须使用强制转换解决:
cpp
using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundB = std::bind(static_cast<SetAlarm3ParamType>(setAlarm),
std::bind(std::plus<>(),
steady_clock::now(),
1h),
_1,
30s);
而lambda无需任何修改就能正常工作【1†source】。
捕获语义
std::bind总是按值拷贝实参,要按引用传递需使用std::ref【5†source】。而lambda可以明确指定值捕获或引用捕获:
cpp
Widget w;
auto compressRateL = [w](CompLevel lev) { return compress(w, lev); }; // 值捕获明确
auto compressRateB = std::bind(compress, std::ref(w), _1); // 引用捕获不明显
C++11中std::bind的合理用例
在C++11中,std::bind仍有以下两个合理用例【1†source】:
- 移动捕获:C++11的lambda不支持移动捕获,可以通过std::bind模拟
- 多态函数对象:绑定带有模板化函数调用运算符的对象
cpp
class PolyWidget {
public:
template<typename T>
void operator()(const T& param);
};
PolyWidget pw;
auto boundPW = std::bind(pw, _1); // 接受任意类型参数
但在C++14中,这两个用例都可以用lambda更好实现:
cpp
auto boundPW = [pw](const auto& param) { pw(param); }; // C++14多态lambda
结论
与std::bind相比,lambda具有以下优势:
- 更易读,更具表达力
- 通常更高效
- 更直观的参数传递和捕获语义
- 更好的重载处理能力
在C++14及更高版本中,几乎没有使用std::bind的合理场景。对于仍在使用C++11的项目,仅在需要移动捕获或绑定多态函数对象时考虑std::bind。
记住:在大多数情况下,lambda应该是你的首选工具,std::bind已成为历史遗产,应逐渐被替代。