【读书笔记】《Effective Modern C++》第六章 Lambda Expressions
《Effective Modern C++》第六章 Lambda Expressions
Lambda 表达式是 C++11 引入的一项革命性特性,使得函数对象的定义和使用更加简洁灵活。到了 C++14 和 C++17,Lambda 又逐步具备了更强的表达力(如 init capture、泛型 Lambda、constexpr lambda 等)。
Item 31:避免使用默认捕获模式(Avoid default capture modes)
背景
Lambda 表达式可以通过以下方式捕获外部变量:
[]
不捕获任何外部变量;[=]
默认按值捕获所有可见变量;[&]
默认按引用捕获所有可见变量;[x, &y]
显式捕获x
按值,y
按引用。
问题
默认捕获模式易隐藏错误和副作用。
例:
void example() {int x = 0;auto lambda = [=]() { std::cout << x << std::endl; };x = 42;lambda(); // 输出 0,但你是否意识到 x 是按值复制?
}
或:
std::vector<std::function<void()>> actions;
for (int i = 0; i < 5; ++i) {actions.push_back([&]() { std::cout << i << std::endl; }); // 所有输出5
}
由于 i
被引用捕获,Lambda 延迟执行时访问的是同一个变量。
建议
始终显式捕获所需变量,不使用 [=]
或 [&]
:
auto lambda = [x]() { std::cout << x << std::endl; };
Item 32:使用 init capture 将对象移动进闭包(Use init capture to move objects into closures)
问题
传统 C++11 Lambda 无法直接将 std::unique_ptr
或其他移动专属类型传入闭包:
std::unique_ptr<int> up(new int(42));
auto lambda = [up]() { ... }; // 错误:复制被禁
C++14 引入 init capture(初始化捕获),可支持移动语义。
解决方式(C++14+)
auto lambda = [up = std::move(up)]() {*up += 1;std::cout << *up << std::endl;
};
这样 up
被移动进闭包,生命周期受控于 Lambda 本身,避免复制和潜在悬空指针。
额外用途
- 可以用
init capture
构造并传入一个临时对象; - 适用于绑定外部资源或限定作用域对象。
Item 33:在 auto&&
参数上使用 decltype
+ std::forward
(Use decltype on auto&& parameters to std::forward them)
背景
C++14 支持泛型 Lambda:
auto lambda = [](auto&& param) {use(param); // 是否转发了值类别?
};
此时 param
是一个 universal reference,但 param
是左值,调用 use()
时会发生不必要的拷贝或失效。
正确方式:配合 decltype
和 std::forward
auto lambda = [](auto&& param) {use(std::forward<decltype(param)>(param));
};
这样可以保留调用者传入参数的值类别,实现完美转发。
实际场景
- 泛型工具函数中的转发器;
- 延迟执行、缓存包装器;
- 函数适配器。
注意
auto&&
本身不能传递值类别信息;decltype(param)
得到的类型才能判断是左值引用还是右值引用;- 对 universal reference 参数始终用
std::forward<decltype(param)>
。
Item 34:优先使用 Lambda 而不是 std::bind(Prefer lambdas to std::bind)
背景
std::bind
用于部分函数绑定,在 C++98 和 C++11 被广泛使用,但语法复杂,类型不透明,可读性差。
例:
auto f = std::bind(&MyClass::method, obj, _1, 42);
_1
来自<functional>
,不直观;- 产生的类型复杂,难调试;
- 编译错误难排查。
替代方案:Lambda 表达式
auto f = [obj](int x) {obj.method(x, 42);
};
- 更易读、更明确;
- 支持捕获、移动、嵌套等功能;
- 更易于调试和出错时诊断。
实践建议
始终用 Lambda 替代 std::bind,除非遇到非常规的模板绑定逻辑或特定 legacy 接口。
总结
Lambda 表达式是现代 C++ 最强大也是最常用的语法工具之一。本章提供了如下关键建议:
条目 | 建议 |
---|---|
31 | 避免 [=] 或 [&] ,改用显式捕获 |
32 | 使用 init capture ([x = std::move(x)] ) 移动资源进闭包 |
33 | 对泛型参数使用 decltype + std::forward 保持值类别 |
34 | 优先使用 Lambda,避免使用 std::bind |
通过正确使用 Lambda,你不仅可以简化代码、提升性能,更能避免因隐式捕获、复制、转发失败而带来的微妙错误。这是写出现代、高效、健壮 C++ 的基础能力之一。