【问题标题】:Forward declaration of lambdas in C++C++ 中 lambda 的前向声明
【发布时间】:2016-11-22 06:26:39
【问题描述】:

在 C++ 中,可以将函数的声明和定义分开。例如,声明一个函数是很正常的:

int Foo(int x);

Foo.h 中并在Foo.cpp 中实现它。是否可以用 lambdas 做类似的事情?例如,定义一个

std::function<int(int)> bar;

bar.h 中并在bar.cpp 中实现它:

std::function<int(int)> bar = [](int n)
{
    if (n >= 5) 
        return n;
    return n*(n + 1);
};

免责声明:我在 C# 中使用过 lambda,但在 C++ 中使用的并不多。

【问题讨论】:

  • 如果你要给 lambda 起一个名字,你也可以把它变成一个普通的函数。
  • @Brian 我知道,这是有道理的。我问这个问题的主要原因是好奇,我想知道它的可能性。
  • 仅供参考; std::function&lt;int(int)&gt; 能够存储 lambda;但 lambda 没有 std::function&lt;...&gt; 类型;而是拥有自己独特的未命名类型。如果推导出变量的类型(通过auto 或模板参数类型推导),您可以将 lambda 直接存储在变量中。 std::function 有一个模板构造函数,它能够通过模板参数类型推导的原理来获取一个 lambda 参数。
  • 我们来看这个:int b; b = 13;您声明名称,而不是文字。您将如何声明 13,为什么?您的 lambda 类似于 13,bar 类似于 b
  • 顺便说一下,C#中的区别是一样的。您似乎将 (C#) 委托与 (C#) lambda 混淆(本质上是:“匿名委托实现”)。在 C# 中,可以将 lambdas 分配给委托(变量),但它们不是一回事,就像在 C++ 中一样。

标签: c++ c++11 lambda declaration definition


【解决方案1】:

lambdas 的声明和定义不能分开,也不能转发声明。它的类型是一个独特的未命名的闭包类型,用 lambda 表达式声明。但是您可以使用 std::function 对象来做到这一点,该对象旨在能够存储任何可调用目标,包括 lambda。

正如您的示例代码所示,您一直在使用 std::function,请注意,对于这种情况,bar 确实是一个全局变量,您需要在头文件中使用 extern 使其成为声明(不是定义)。

// bar.h
extern std::function<int(int)> bar;     // declaration

// bar.cpp
std::function<int(int)> bar = [](int n) // definition
{
    if (n >= 5) return n;
    return n*(n + 1);
};

再次注意,这不是 lambda 的单独声明和定义;它只是一个类型为std::function&lt;int(int)&gt; 的全局变量bar 的单独声明和定义,它是从一个lambda 表达式初始化的。

【讨论】:

  • @Mzhr 请注意,lambda 的类型不是 std::function,而是 lambda 独有的未命名类型。 std::function 是任何可调用对象的多态包装器。
【解决方案2】:

严格来说你不能

引用cpp reference

lambda 表达式是一个纯右值表达式,其值为 (直到 C++17),其结果对象是(自 C++17 起)未命名的临时对象 唯一的未命名非联合非聚合类类型,称为闭包 类型,以最小的形式声明(为了 ADL 的目的) 包含 lambda 的块作用域、类作用域或命名空间作用域 表达

所以 lambda 是一个未命名的临时对象。您可以将 lambda 绑定到左值对象(例如 std::function),并且通过有关变量声明的常规规则,您可以将声明和定义分开。

【讨论】:

  • 感谢您的详细回复。我可以问一下你的报价来源吗?它来自 C++ 规范吗?
【解决方案3】:

前向声明不是正确的术语,因为 C++ 中的 lambda 是对象,而不是函数。代码:

std::function<int(int)> bar;

声明一个变量,你不必为它赋值(该类型的默认值是“指向无函数的指针”)。您甚至可以编译对它的调用...例如代码:

#include <functional>
#include <iostream>

int main(int argc, const char *argv[]) {
    std::function<int(int)> bar;
    std::cout << bar(21) << "\n";
    return 0;
}

将编译干净(但当然会在运行时表现得很疯狂)。

也就是说,您可以将 lambda 分配给兼容的 std::function 变量并添加例如:

bar = [](int x){ return x*2; };

在调用之前将产生一个可以正常编译并生成输出 42 的程序。

关于 C++ 中的 lambdas 的一些非显而易见的事情可能会令人惊讶(如果您知道其他具有此概念的语言)

  • 每个 lambda [..](...){...} 都有不同的不兼容类型,即使签名完全相同。例如,您不能声明 lambda 类型的参数,因为唯一的方法是使用类似 decltype([] ...) 的东西,但是没有办法调用该函数,因为调用站点上的任何其他 []... 形式都是不兼容的。 std::function 解决了这个问题,因此如果您必须传递 lambdas 或将它们存储在容器中,您必须使用 std::function

  • Lambda 可以通过值(但它们是 const,除非您声明 lambda mutable)或通过引用(但保证被引用对象的生命周期不会短于lambda 取决于程序员)。 C++ 没有垃圾收集器,这是正确解决“upward funarg”问题所需要的(您可以通过捕获智能指针来解决问题,但必须注意引用循环以避免泄漏)。

  • 与其他语言不同,可以复制 lambdas,当您复制它们时,您会对其内部捕获的按值变量进行快照。这对于可变状态来说可能非常令人惊讶,这就是我认为默认情况下捕获的按值值是const 的原因。

一种合理化和记住有关 lambda 的许多细节的方法是这样的代码:

std::function<int(int)> timesK(int k) {
    return [k](int x){ return x*k; };
}

基本上是这样的

std::function<int(int)> timesK(int k) {
    struct __Lambda6502 {
        int k;
        __Lambda6502(int k) : k(k) {}
        int operator()(int x) {
            return x * k;
        }
    };
    return __Lambda6502(k);
}

有一个细微的差别,即使是 lambda 捕获引用也可以被复制(通常包含作为成员的引用的类不能)。

【讨论】:

    猜你喜欢
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    • 1970-01-01
    • 2010-12-29
    • 2021-12-25
    • 1970-01-01
    相关资源
    最近更新 更多