c++ - 如果全局函数使用非局部变量,那么它是闭包是否正确?

标签 c++ lambda closures

我对C++中的闭包感到非常困惑。我已经读过What is a 'Closure'?,但是几乎所有答案都涉及JavaScript,但是我认为C++和JavaScript之间的闭包有些区别。因此,我发现很难将闭包的JavaScript描述与C++相匹配。

例如,几乎所有答案都以函数返回函数为例来演示JavaScript中的闭包。但是我在C++中找不到类似的模式。

而且,在JavaScript中没有所谓的“捕获列表”。


有人告诉我

  • ,如果一个函数使用非局部变量(来自外部范围或全局范围),则它是一个闭包。这是正确的吗?

  • 范例1:
        int a = 3;
    
        int am_I_a_closure(int c){
            return c + a;
        }
    
        int main(){
        }
    
  • 为什么需要捕获列表? C++中的lambda不能像JavaScript嵌套函数一样工作吗?
    或者换一种说法,C++中的lambda不能像全局函数访问全局(非局部)变量那样工作吗?

  • 我的意思是,通过普通的名称查找过程,如果在当前作用域中找不到名称,则在外部作用域中找到它,然后在外部作用域中找到更多...

    为什么需要捕获列表?为什么需要捕获外部作用域变量?不能通过常规名称查找来完成吗?

    范例2:
    int main(){
        int a = 3;
        {
            int b = 5;
            {
                int c = 4;
                {
                    std::cout << a+b+c <<std::endl;
                }
            }
        }
    }
    
    

    范例3:
    int main(){
        std::vector<int> values = {1,5,3,4,3};
        int a = 3;
        std::find_if(values.begin(), values.end(), [](int value) {return value > a; }); //Error, `a` is not captured.
    }
    

    同样,在示例3中,为什么需要捕获a而不是像示例1和示例2中那样进行常规名称查找?

    最佳答案

    重要的是要理解“闭包”这个概念在函数式编程中具有非常特殊的含义。但是,C++不是一种功能语言。它并不十分在乎严格遵守函数式编程术语。它仅定义了各种功能,其中某些功能可能无法很好地映射到该术语。

    JavaScript和C++是不同的语言。在JavaScript中,函数具有称为“第一类对象”的属性。这意味着,当您执行代码以创建“函数”时,正在创建一个表示该函数的对象。包含函数的变量与包含字符串的变量或包含数组的变量或其他变量根本没有区别。您可以覆盖包含带有数组的函数的变量,反之亦然。

    特别是,作为一流对象的功能在其创建时就可以具有与其相关联的状态。如果此类函数超出了访问局部变量的范围,则可以将该范围存储为函数状态的一部分;当您尝试在函数中使用该变量时,将自动访问该状态。因此看来您正在“超出”函数的范围,但您并未达到;示波器是随您带入的,而您只是在访问它。

    在C++中,函数不是一流的对象。您可以获取指向函数的指针,但是函数指针与对象指针明显不同(甚至不需要在两者之间进行广播就有效)。就C++语言而言,不是“创建”或“销毁”函数。从程序开始到结束,每个功能始终存在。

    C++函数可以访问全局变量,但这是因为它们是全局的。全局变量的位置在编译/链接时被烘焙到可执行文件中,因此无需将特殊状态与函数一起存储即可访问它。

    但是,C++确实有一个有用的概念,可以帮助创建一流的函数对象。即,类类型可以使函数调用运算符operator()重载。这样,就可以像调用一个函数一样调用类的实例。类实例是对象,可以具有内部状态(aka:成员变量),并且operator()重载只是该类型的成员函数。

    考虑到所有这些,您可以创建一些模拟适当范围的函数对象的对象。您只需要一个具有成员变量的类,该成员变量对应于其引用的函数范围之外的变量。这些成员可以在类的构造函数中初始化,方法是将外部值传递给构造函数。这样便有了一个可以调用的有效对象,它可以使用其成员变量访问那些“外部”变量。

    这就是C++ lambda的全部内容。它使用“精巧,整洁”的语法包装了所有这些内容。它为您编写了一个类;它为您编写从外部世界“捕获”的成员变量,并调用构造函数并为您传递这些变量。

    但是,C++是一种努力不使某些东西变得比您需要的东西昂贵的语言。您使用的外部变量越多,lambda将需要更多的内部成员变量,因此类越大,初始化/复制/等花费的时间就越长。因此,如果要使用某个外部变量(作为成员变量实现),则C++要求您明确列出它(以便知道要捕获它)或使用默认的捕获机制[=][&] (这样一来,您就明确放弃了对不小心使Lambda类型变大和/或变慢的抱怨的权利)。

    此外,在JavaScript中,所有内容都是引用。变量存储对数组,函数,字典等的引用。JavaScript是一种基于引用的语言。

    C++是一种面向值(value)的语言。 JavaScript中的变量引用对象。 C++中的变量是一个对象。在C++中,您不能将一个对象替换为另一个。您可以复制对象的值,但仍然是该对象。

    这样,lambda应该如何捕获特定变量就变得很重要。您可以通过值(将值复制到隐藏成员中)或通过引用(引用对象)来捕获变量。

    这一点特别重要,因为C++不会被垃圾收集。这意味着,仅仅因为您引用了一个对象,并不意味着该对象仍然存在。如果您在堆栈上有一个变量,并且获得了对它的引用,并且该引用存在于堆栈变量超出作用域的范围之外,则该引用现在无用了。在JavaScript中,由于进行垃圾收集会很好。但是C++不会那样做。您有一个无法使用的销毁对象的引用。

    因此,如果您希望lambda捕获局部变量,并且希望lambda持续到变量不再存在的地步,则需要按值而不是按引用捕获此类变量。

    通过值或引用捕获取决于您如何在捕获列表中列出变量。 &x表示按引用捕获,而x是按值捕获。默认捕获[=]表示默认情况下按值捕获,[&]表示默认情况下引用捕获。

    关于c++ - 如果全局函数使用非局部变量,那么它是闭包是否正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61605918/

    相关文章:

    JavaScript 闭包

    c++ - 查找两点之间的整数坐标

    指向 if 语句中变量的 C++ 指针

    c++ - 是否有独立的 C++ 源代码预处理器?

    ios - Swift 闭包和执行顺序

    javascript - 循环和闭包。对于和变量

    c++ - 我应该始终使用带有自己索引的一维 vector ,还是可以使用多维 vector ?

    node.js - 亚马逊AWS Lambda : Cannot find "Request"

    c# - 如何从 Expression<Action<T>> 获取接口(interface)成员名称

    C# 到 Lambda - 计算小数位数/第一位有效小数