d闭包不尊重域
int main() {
void delegate()[] dgs;
for (int i = 0; i < 10; i++) {
dgs ~= () {
import std.stdio;
writeln(i);
};
}
dgs ~= () {
import std.stdio;
writeln("带缓存变量");
};
for (int i = 0; i < 10; i++) {
int index = i;
dgs ~= () {
import std.stdio;
writeln(index);
};
}
foreach (ref dg; dgs) {
dg();
}
return 0;
}
//输出:
10
10
10
10
10
10
10
10
10
10
//用缓存变量
9
9
9
9
9
9
9
9
9
9
期望10系列,因为每次捕捉相同i变量.而9系列,不是.
每次迭代都会声明新变量,因此应创建新闭包
不这样,导致奇怪,如不变变量中的改变(在@safe中应禁止或过时).期望为10系列与0到9.
语义与C#类似,
js中:
var dgs = [];
for (var i = 0; i < 10; i++) {
dgs.push(function() {
console.log(i);
});
}
dgs.push(function() {
console.log("带缓存变量");
});
for (var i = 0; i < 10; i++) {
var index = i;
dgs.push(function() {
console.log(index);
});
}
for (var dg of dgs) {
dg();
}
与最上面输出一样.D行为与其他语言差不多.把var改为let更接近.
D当前传递可变给λ,这样,当前值为λ状态一部分.
代码是易错的,js中var在域变量中声明,用创建正确域的let声明,再试试.
重写为不用闭包的:
int test() @safe {
int j;
int*[20] ps;
for (int i = 0; i < 10; i++) {
ps[j++] = &i;
}
for (int i = 0; i < 10; i++) {
int index = i;
ps[j++] = &index;
}
int x;
foreach (p; ps) {
x += *p;
}
return x;
}
代码与用引用和域等价,用-dip1000编译.产生:
错误:用更长生命期赋值i变量地址给ps.这是闭包``示例行为,因为闭包也存储了变量指针.
指针周围有抽象(如数组,闭包,引用,出,类引用等)时,用指针显式重写,来确定应怎样.然后就暴露了可能奇怪的行为.且行为应匹配.
诚然,编译器中有个有时会把闭包引用变量放进垃集分配的闭包中的特殊组件,但并不表明,在函数域结束前可离开域.未为他们造闭包,且此功能比加值更复杂,因此应是个错误.
D中,在堆上分配闭包.
同意是八哥,你用新状态创建闭包/λ.
循环中,为多个闭包创建仅一个共享状态的原因是啥?用例?
如果,D不破坏当前行为,可加与闭包完全匹配的新的λ关键字,它在创建时取全新状态,只需更改编译时生成代码.
C++按值或按引用更可行.
如果捕捉,C++的标::函数也在堆中分配,等价C++代码也在循环中分配.
函数可在堆上分配,但要根据实现/λ状态大小.仅单个分配不会在堆上分配.
函数是否分配不相关.
#include <functional>
#include <vector>
#include <iostream>
int main(int argc, char** argv) {
std::vector<std::function<void()>> dgs;
for (int i = 0; i < 10; i++) {
// 按引用捕捉,与`D`语义一样
dgs.emplace_back([&i] () {
std::cout << i << "\n";
});
}
dgs.emplace_back([] () {
std::cout << "带缓存变量" << "\n";
});
for (int i = 0; i < 10; i++) {
int index = i;
// 同上.
dgs.emplace_back([&index] () {
std::cout << index << "\n";
});
}
for (auto& dg: dgs) {
dg();
}
return 0;
}
用clang调试构建打印10们,后面是9们.使用gcc调试构建会打印垃圾值.两个的优化构建打印垃圾值.沃尔特的重写清楚地说明了原因.,D的输出至少是可预测的非垃圾.
注意注释,这是问题所在.D的捕捉始终是按引用,因此等效C++代码也应这样.如果按复制捕捉,可以肯定,在"用缓存变量"后看到[0,10),但是代码不是等效的.
方法是转换循环体至匿名λ调用来强制新帧:
for (int i = 0; i < 10; i++) (int index) {
dgs ~= () {
import std.stdio;
writeln(index);
};
} (i);
但就不能用break/continue了.
理想是,在D中按复制或移动捕捉.
.C++做到了这一点:禁止隐式捕捉并让程序员准确指定如何捕捉.D应该这样.
建议,强制按引用捕捉循环变量闭包应是@system,@安全代码应报错误.同时,还应有进一步增强.应允许不要求的愚蠢的包装器.
D中λ不是按值,而是按引用捕捉.
也是循环变量的直接结果,"i"和"index"虽然在域尾消失了,但在相同位置重建.因此,所有λ引用指向每个引用指向的相同位置.
问题是λ生命期都超过了i和index变量生命期.
赋值引用i的λ给比i生命期更长的dgs[]时,应发出错误.
@safe
void test() {
int delegate() dg;
foreach (i; 0 .. 10) {
dg = () { return i; };
}
}
上面简化版本,应编译失败.
即使在垃集堆上分配闭包也没用,因为每个相同行为λ只会共享唯一的分配.
浙公网安备 33010602011771号