通过引用捕获循环中的闭包?

时间:2015-04-20 22:17:11

标签: loops delegates closures d

D中的委托似乎通过引用捕获本地值,这在循环中创建闭包时会产生奇怪的副作用:最后,您有n个闭包具有相同的上下文指针。举个例子:

import std.stdio;
alias Closure = void delegate();
Closure[] closures;

void main(){
    foreach(a; ["Je", "Tu", "En", "Oui", "Na"])
        closures ~= {write(a);};
    foreach(c; closures)
        c();
    writeln(" batman");
}

这会打印NaNaNaNaNa batman

这是预期的行为吗?如果是这样,我将如何处理它以便正确打印所有数组元素?

当使用带有计数器变量的for循环时,它变得更有趣,最后i等于数组大小,并且在委托中使用closures[i]时会抛出一个越界错误

1 个答案:

答案 0 :(得分:3)

是的,这是预期的行为(编辑:......它实际上是一个古老的已知错误!https://issues.dlang.org/show_bug.cgi?id=2043所以只能预料到它会发生并且你可以很容易地习惯它,但它实际上并非如此。 ; t应该发生)并且在其他语言中也可以看到,所以这是一个很好的原则。

要在循环中获取变量的单独副本,请调用另一个返回要存储的委托的函数,并将循环变量传递给它。

import std.stdio;
alias Clojure = void delegate();
Clojure[] clojures;

void main(){
    foreach(a; ["Je", "Tu", "En", "Oui", "Na"])
        clojures ~= ((b) => { write(b);})(a);
    foreach(c; clojures)
        c();
    writeln(" batman");
}

clojures ~= ((b) => { write(b);})(a);行已更改:它定义了一个返回委托的快速委托。额外的函数返回函数关闭循环状态的快照,而不仅仅是函数级的局部变量。

我在JavaScript中也经常使用它:

 function makeHandler(item) {
      return function() {
         // use item here
      };
 }

var array = [1,2,3];
for(var I = 0; I < array.length; I++)
   foo.addEventListener("click", makeHandler(array[I]));

这与D的原因相同,只是在不同的语法中,并分解为更大的功能,而不是尝试将其作为一个单行。

我们定义一个函数,它返回一个使用捕获的循环变量的函数。在使用点,我们调用一个函数,该函数返回为以后存储的委托。

在简写D语法((b) => { write(b);})(a);中,(b) => ...是javascript中看到的makeHandler函数。它返回的{ write(b); }是JS中return function() { ... }的简写(BTW,相同的JS语法基本上也适用于离散D,你可以用delegatefunction关键字编写一个简写的东西。但是function并没有捕获变量,delegate会这样做..)

然后,最后,它周围的括号和最后的(a)只是调用函数。里面的东西与makeHandler相同,(...)(a)称之为;它是makeHadndler(a)