第四章 Lambda函数

# 第四章 Lambda函数

接下来的两个问题将围绕Lambda函数展开,这是C++11中最重要的特性之一。回答这些问题的前提是你了解Lambda函数是什么。

# 问题30:什么是立即调用的Lambda函数?

可以说,它们是被立即调用的Lambda函数。好吧,但“立即”是什么意思呢? 这意味着Lambda函数甚至不会被变量存储和引用,而是在创建它的地方,马上就调用它。 下面这个不是立即调用的Lambda函数:

auto  l = [](){return  42;}; 
int  fortyTwo = l(); 
1
2

在上述例子中,你可以复用这个Lambda函数,可以传递它,根据需要随时调用它。 另一方面,在下面这个例子中,你立即调用了Lambda函数,这意味着你不会存储Lambda函数本身。根据定义,立即调用的Lambda函数(IILFs)不能被存储。如果它们被存储了,就不是立即调用的。

auto  fortyTwo = [](){return  42;}(); 
1

为什么这个概念很强大且值得一提呢? 它有助于对变量进行复杂的初始化。这很重要,因为帮助C++编译器的一个好方法是将所有不应该改变的变量声明为const。 在大多数情况下,这很简单,你只需在类型旁边加上const,并当场初始化变量。但你可能会遇到手中有不同可能值的情况。 让我们从一个简单的例子开始。

// Bad  Idea
std::string someValue; 
if  (caseA) {
    return  std::string{"Value A"};
} else  {
    return  std::string{"Value B"};
}
1
2
3
4
5
6
7

这样不好,因为这样的话someValue不是const类型的。我们能把它变成const类型吗?当然可以。我们可以使用三元运算符。

const  std::string someValue = caseA ? std::string{"Value A"} : std::string{"Value B"};
1

非常简单。 但是如果有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"};
    }
}();
1
2
3
4
5
6
7
8
9

通过这种方式,你可以对const变量进行复杂的初始化,而且你不必为初始化逻辑寻找合适的位置或命名。 在性能方面,我们获得了使用const变量的所有性能优势,与三元运算符或辅助函数相比,没有任何性能损失。你可以在这里找到更多关于性能分析的详细信息⁶⁵ 。

# 问题31:Lambda表达式有哪些可用的捕获方式?

首先,一个Lambda表达式看起来是这样的:

capture -> returnType
1

捕获列表(capture)是一个由零个或多个捕获项组成的逗号分隔列表,可选择以捕获默认值开头。捕获列表定义了在Lambda函数体内可以访问的外部变量。 仅有的捕获默认值有:

  • &(隐式地通过引用捕获使用的自动变量)和
  • =(隐式地通过拷贝捕获使用的自动变量)。 如果存在任何一个捕获默认值,当前对象(*this)可以被隐式捕获。如果是隐式捕获,它总是通过引用捕获,即使捕获默认值是=。不过从C++20开始,使用捕获默认值=隐式捕获*this已被弃用。 以下是可用的捕获类型:
  • 按值捕获(自C++11起)
int  num;
auto  l = [num](){}
1
2
  • 作为包扩展的按值捕获(自C++11起)
template  <typename  Args>
void  f(Args... args) {
    auto  l = [args...] {
        return  g(args...);
    };
    l();
}
1
2
3
4
5
6
7
  • 按引用捕获(自C++11起)
int  num=42;
auto  l = [&num](){};
1
2
  • 作为包扩展的按引用捕获(自C++11起)
template  <typename  Args>
void  f(Args... args) {
    auto  l = [&args...] { return  g(args...);};
    l();
}
1
2
3
4
5
  • 对当前对象的按引用捕获(自C++11起)
auto  l = [this](){};
1
  • 带初始化器的按值捕获(自C++14起)
auto  l = [num=5](){};
1
  • 带初始化器的按引用捕获(自C++14起)
int  num=42;
auto  l = [&num2=num](){};
1
2
  • 对当前对象的按值捕获(自C++17起)
auto  l = [*this](){};
1
  • 带初始化器且作为包扩展的按值捕获(自C++20起)
template  <typename  Args>
auto  delay_invoke_foo(Args... args) {
    return   [...args=std::move(args)]() -> decltype(auto) {
        return  foo(args...);
    };
}
1
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...);
    };
}
1
2
3
4
5
6
上次更新: 2025/03/27, 20:29:48

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>