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; };
}
}
上面简化
版本,应编译失败.
即使在垃集
堆上分配闭包
也没用,因为每个相同行为
λ只会共享唯一
的分配.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现