如果一个全局函数使用非局部变量,那么它就是一个闭包,这是否正确?
Is it correct that if a global function uses non-local variables, then it's a closure?
我一直很困惑 C++ 中的闭包是什么。我读过这个 What is a 'Closure'? 但几乎所有答案都指的是 JavaScript 但我认为 C++ 和 JavaScript 之间的闭包存在一些差异。所以我发现很难将闭包的 JavaScript 描述与 C++ 相匹配。
例如JavaScript中几乎所有的答案都是以函数返回函数为例来演示闭包。但我没有在 C++ 中找到类似的模式。
更何况,在JavaScript中并没有"capture list"这个东西。
- 有人告诉我,如果一个函数使用非局部变量(来自外部作用域或全局作用域),那么它就是一个闭包。正确吗?
示例 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 中的正常名称查找?
了解 "closure" 是一个在函数式编程中具有非常特定含义的概念很重要。然而,C++ 不是函数式语言;它并不关心严格遵守函数式编程术语。它只是定义了各种功能,其中一些可能会或可能不会很好地映射到该术语。
JavaScript和C++是不同的语言。在 JavaScript 中,函数有一个 属性 称为 "first-class object"。这意味着,当您执行创建 "function" 的代码时,您正在创建一个对象,该对象表示包含函数的 function.A 变量与包含字符串的变量或包含函数的变量从根本上没有区别数组或其他任何东西。您可以用数组覆盖包含函数的变量,反之亦然。
特别是,作为 first-class 对象的函数可以在创建时具有与其关联的状态。如果这样的函数超出其范围访问局部变量,则该范围可以作为函数状态的一部分存储;当您尝试在函数中使用该变量时,将自动访问此状态。所以看起来你正在达到函数范围的 "out" ,但你没有;示波器 "in" 随身携带,您只是访问它。
在 C++ 中,函数不是 first-class 对象。您可以获得指向函数的指针,但函数指针与 object 指针明确不同(甚至不需要在两者之间进行转换是有效的)。就 C++ 语言而言,函数不是 "created" 或 "destroyed";每个函数始终存在,从程序开始到结束。
C++ 函数可以访问全局变量,但那是因为它们是 global。全局变量的位置在 compile/link 时被烘焙到可执行文件中,因此不需要与函数一起存储特殊状态即可访问它。
但是,C++ 确实有一个有用的概念,可以帮助创建第一个 class 函数对象的效果。即,class 类型可以重载函数调用运算符 operator()
。这允许 class 的实例被调用,就好像它是一个函数一样。 Class 实例是对象并且可以具有内部状态(又名:成员变量),operator()
重载只是该类型的成员函数。
考虑到所有这些,您可以创建一些东西来模拟适当作用域的函数对象。您所需要的只是一个 class ,它具有与它引用的函数范围之外的变量相对应的成员变量。通过将外部值传递给构造函数,可以在 class 的构造函数中初始化这些成员。然后你有一个可以调用的有效对象,它可以通过使用它的成员变量来访问那些 "external" 变量。
这就是 C++ lambda 的全部内容。它用 "nice, neat" 语法包装了所有这些。它为你写了一个class;它从外部世界为你写成员变量"capture",它调用构造函数并为你传递这些变量。
但是,C++ 是一种努力不让某些东西变得比你需要的更昂贵的语言。您使用的外部变量越多,lambda 需要的内部成员变量就越多,因此 class 就会越大,initialize/copy/etc 所需的时间也会越长。因此,如果你想使用一些外部变量(作为成员变量实现),C++ 要求你显式列出它(以便你知道你打算捕获它)或使用默认捕获机制 [=]
或 [&]
(这样你就明确放弃了抱怨不小心使你的 lambda 类型变大 and/or 变慢的权利)。
此外,在JavaScript中,一切都是参考。变量存储对数组、函数、字典等的引用。JavaScript 是一种基于引用的语言。
C++ 是一种面向价值的 语言。 JavaScript 中的一个变量引用了一个对象; C++ 中的变量 是一个对象 。在 C++ 中,您不能用一个对象替换另一个对象;你可以复制一个对象的值,但它仍然是那个对象。
因此,lambda 应该如何捕获特定变量变得相关。您可以按值(将值复制到隐藏成员)或按引用(引用对象)捕获变量。
这特别重要,因为 C++ 不会被垃圾回收。这意味着,仅仅因为您有一个对象的引用并不意味着该对象 仍然存在 。如果您在堆栈上有一个变量,并且您获得了对它的引用,并且该引用存在于堆栈变量超出范围的位置之后......该引用现在无用。在 JavaScript 中,由于垃圾收集,它会很好。但是 C++ 不会那样做。您有一个对已销毁对象的引用,该对象无法使用。
因此,如果您希望 lambda 捕获局部变量 并且 您希望 lambda 持续到变量不再存在的点之后,您将需要通过以下方式捕获此类变量值,不是引用。
按值或按引用捕获取决于如何在捕获列表中列出变量。 &x
表示按引用捕获,而 x
是按值捕获。默认捕获[=]
表示默认按值捕获,[&]
表示默认引用捕获。
我一直很困惑 C++ 中的闭包是什么。我读过这个 What is a 'Closure'? 但几乎所有答案都指的是 JavaScript 但我认为 C++ 和 JavaScript 之间的闭包存在一些差异。所以我发现很难将闭包的 JavaScript 描述与 C++ 相匹配。
例如JavaScript中几乎所有的答案都是以函数返回函数为例来演示闭包。但我没有在 C++ 中找到类似的模式。
更何况,在JavaScript中并没有"capture list"这个东西。
- 有人告诉我,如果一个函数使用非局部变量(来自外部作用域或全局作用域),那么它就是一个闭包。正确吗?
示例 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 中的正常名称查找?
了解 "closure" 是一个在函数式编程中具有非常特定含义的概念很重要。然而,C++ 不是函数式语言;它并不关心严格遵守函数式编程术语。它只是定义了各种功能,其中一些可能会或可能不会很好地映射到该术语。
JavaScript和C++是不同的语言。在 JavaScript 中,函数有一个 属性 称为 "first-class object"。这意味着,当您执行创建 "function" 的代码时,您正在创建一个对象,该对象表示包含函数的 function.A 变量与包含字符串的变量或包含函数的变量从根本上没有区别数组或其他任何东西。您可以用数组覆盖包含函数的变量,反之亦然。
特别是,作为 first-class 对象的函数可以在创建时具有与其关联的状态。如果这样的函数超出其范围访问局部变量,则该范围可以作为函数状态的一部分存储;当您尝试在函数中使用该变量时,将自动访问此状态。所以看起来你正在达到函数范围的 "out" ,但你没有;示波器 "in" 随身携带,您只是访问它。
在 C++ 中,函数不是 first-class 对象。您可以获得指向函数的指针,但函数指针与 object 指针明确不同(甚至不需要在两者之间进行转换是有效的)。就 C++ 语言而言,函数不是 "created" 或 "destroyed";每个函数始终存在,从程序开始到结束。
C++ 函数可以访问全局变量,但那是因为它们是 global。全局变量的位置在 compile/link 时被烘焙到可执行文件中,因此不需要与函数一起存储特殊状态即可访问它。
但是,C++ 确实有一个有用的概念,可以帮助创建第一个 class 函数对象的效果。即,class 类型可以重载函数调用运算符 operator()
。这允许 class 的实例被调用,就好像它是一个函数一样。 Class 实例是对象并且可以具有内部状态(又名:成员变量),operator()
重载只是该类型的成员函数。
考虑到所有这些,您可以创建一些东西来模拟适当作用域的函数对象。您所需要的只是一个 class ,它具有与它引用的函数范围之外的变量相对应的成员变量。通过将外部值传递给构造函数,可以在 class 的构造函数中初始化这些成员。然后你有一个可以调用的有效对象,它可以通过使用它的成员变量来访问那些 "external" 变量。
这就是 C++ lambda 的全部内容。它用 "nice, neat" 语法包装了所有这些。它为你写了一个class;它从外部世界为你写成员变量"capture",它调用构造函数并为你传递这些变量。
但是,C++ 是一种努力不让某些东西变得比你需要的更昂贵的语言。您使用的外部变量越多,lambda 需要的内部成员变量就越多,因此 class 就会越大,initialize/copy/etc 所需的时间也会越长。因此,如果你想使用一些外部变量(作为成员变量实现),C++ 要求你显式列出它(以便你知道你打算捕获它)或使用默认捕获机制 [=]
或 [&]
(这样你就明确放弃了抱怨不小心使你的 lambda 类型变大 and/or 变慢的权利)。
此外,在JavaScript中,一切都是参考。变量存储对数组、函数、字典等的引用。JavaScript 是一种基于引用的语言。
C++ 是一种面向价值的 语言。 JavaScript 中的一个变量引用了一个对象; C++ 中的变量 是一个对象 。在 C++ 中,您不能用一个对象替换另一个对象;你可以复制一个对象的值,但它仍然是那个对象。
因此,lambda 应该如何捕获特定变量变得相关。您可以按值(将值复制到隐藏成员)或按引用(引用对象)捕获变量。
这特别重要,因为 C++ 不会被垃圾回收。这意味着,仅仅因为您有一个对象的引用并不意味着该对象 仍然存在 。如果您在堆栈上有一个变量,并且您获得了对它的引用,并且该引用存在于堆栈变量超出范围的位置之后......该引用现在无用。在 JavaScript 中,由于垃圾收集,它会很好。但是 C++ 不会那样做。您有一个对已销毁对象的引用,该对象无法使用。
因此,如果您希望 lambda 捕获局部变量 并且 您希望 lambda 持续到变量不再存在的点之后,您将需要通过以下方式捕获此类变量值,不是引用。
按值或按引用捕获取决于如何在捕获列表中列出变量。 &x
表示按引用捕获,而 x
是按值捕获。默认捕获[=]
表示默认按值捕获,[&]
表示默认引用捕获。