第四章 Lambda函数
# 第四章 Lambda函数
接下来的两个问题将围绕Lambda函数展开,这是C++11中最重要的特性之一。回答这些问题的前提是你了解Lambda函数是什么。
# 问题30:什么是立即调用的Lambda函数?
可以说,它们是被立即调用的Lambda函数。好吧,但“立即”是什么意思呢? 这意味着Lambda函数甚至不会被变量存储和引用,而是在创建它的地方,马上就调用它。 下面这个不是立即调用的Lambda函数:
auto l = [](){return 42;};
int fortyTwo = l();
2
在上述例子中,你可以复用这个Lambda函数,可以传递它,根据需要随时调用它。 另一方面,在下面这个例子中,你立即调用了Lambda函数,这意味着你不会存储Lambda函数本身。根据定义,立即调用的Lambda函数(IILFs)不能被存储。如果它们被存储了,就不是立即调用的。
auto fortyTwo = [](){return 42;}();
为什么这个概念很强大且值得一提呢?
它有助于对变量进行复杂的初始化。这很重要,因为帮助C++编译器的一个好方法是将所有不应该改变的变量声明为const
。
在大多数情况下,这很简单,你只需在类型旁边加上const
,并当场初始化变量。但你可能会遇到手中有不同可能值的情况。
让我们从一个简单的例子开始。
// Bad Idea
std::string someValue;
if (caseA) {
return std::string{"Value A"};
} else {
return std::string{"Value B"};
}
2
3
4
5
6
7
这样不好,因为这样的话someValue
不是const
类型的。我们能把它变成const
类型吗?当然可以。我们可以使用三元运算符。
const std::string someValue = caseA ? std::string{"Value A"} : std::string{"Value B"};
非常简单。 但是如果有3种或更多不同的可能性呢?你有不同的选择,其中之一就是立即调用的Lambda函数。
const std::string someValue = [caseA, caseB] () {
if (caseA) {
return std::string{"Value A"};
} else if (caseB) {
return std::string{"Value B"};
} else {
return std::string{"Value C"};
}
}();
2
3
4
5
6
7
8
9
通过这种方式,你可以对const
变量进行复杂的初始化,而且你不必为初始化逻辑寻找合适的位置或命名。
在性能方面,我们获得了使用const
变量的所有性能优势,与三元运算符或辅助函数相比,没有任何性能损失。你可以在这里找到更多关于性能分析的详细信息⁶⁵ 。
# 问题31:Lambda表达式有哪些可用的捕获方式?
首先,一个Lambda表达式看起来是这样的:
capture -> returnType
捕获列表(capture)是一个由零个或多个捕获项组成的逗号分隔列表,可选择以捕获默认值开头。捕获列表定义了在Lambda函数体内可以访问的外部变量。 仅有的捕获默认值有:
&
(隐式地通过引用捕获使用的自动变量)和=
(隐式地通过拷贝捕获使用的自动变量)。 如果存在任何一个捕获默认值,当前对象(*this
)可以被隐式捕获。如果是隐式捕获,它总是通过引用捕获,即使捕获默认值是=
。不过从C++20开始,使用捕获默认值=
隐式捕获*this
已被弃用。 以下是可用的捕获类型:- 按值捕获(自C++11起)
int num;
auto l = [num](){}
2
- 作为包扩展的按值捕获(自C++11起)
template <typename Args>
void f(Args... args) {
auto l = [args...] {
return g(args...);
};
l();
}
2
3
4
5
6
7
- 按引用捕获(自C++11起)
int num=42;
auto l = [&num](){};
2
- 作为包扩展的按引用捕获(自C++11起)
template <typename Args>
void f(Args... args) {
auto l = [&args...] { return g(args...);};
l();
}
2
3
4
5
- 对当前对象的按引用捕获(自C++11起)
auto l = [this](){};
- 带初始化器的按值捕获(自C++14起)
auto l = [num=5](){};
- 带初始化器的按引用捕获(自C++14起)
int num=42;
auto l = [&num2=num](){};
2
- 对当前对象的按值捕获(自C++17起)
auto l = [*this](){};
- 带初始化器且作为包扩展的按值捕获(自C++20起)
template <typename Args>
auto delay_invoke_foo(Args... args) {
return [...args=std::move(args)]() -> decltype(auto) {
return foo(args...);
};
}
2
3
4
5
6
- 带初始化器且作为包扩展的按引用捕获(自C++20起)
template <typename Args>
auto delay_invoke_foo(Args... args) {
return [&...args=std::move(args)]() -> decltype(auto) {
return foo(args...);
};
}
2
3
4
5
6