我大概理解问题在哪了。
我使希望使用Python代码来说明CPP代码存在的问题的,但是两个在这里有些差异,我混用了一些术语造成误解。
对于Python,在这个示例中,虽然和CPP不同,但在形式上是相同的,在循环中,创建了一个自由变量ai,它被分配在堆上,lambda引用了自由变量ai,但经过三次循环,ai的值被不断改变,最终调用时指向最后一次创建的ai,但如果我们提前del ai或 ai=None,就会导致指向的在堆上的空间被回收,出现错误,你给出的写法,也是Python文档里面的写法,通过复制值的方式,将其传递给lambda,虽然看似相同,但实际上此时ai,被分配在栈上,lambda被调用时,此时ai在新的栈帧中,因此没有问题。
对于CPP,楼主给出了两种错误写法,第一种直接是在栈空间上初始化变量并传递引用给lambda,此时CPP和Python不同,每一次循环时都是新的栈帧,循环结束栈会对变量进行回收,因此那个对象被析构,由于多种原因,三次传递的引用地址指向同一个位置,但在调用时因为对象已经回收,因此实际上他们都是悬空指针。对于第二个用例,虽然使用了共享指针,但却使用引用传递,我猜测这里是因为引用传递没有增加该对象的引用计数,导致其提前被析构,且和上一次同理,都是传递的指针的引用,但是这个指针是在栈空间上,因此都被回收,于是悬空指针,UB。
这两种错误形式非常相似,都是在循环中在lambda中使用变量的引用,因为种种原因,该指针指向的对象被析构,回收且指向的内容发生改变造成的错误,我在这里使用Python用例是期望说明即使使用带有gc语言的开发者,也不会犯这种错误,这种错误真的是低级错误。
更何况,你从直觉上看
for i in ...:
a = A(i)
lambda : a.x()
这种形式难道就不值得怀疑吗?在一个函数内部,两者使用的都是变量的引用,问题是,直观上这个变量的名字在第二次循环上就被改变了。
对于出现这个的原因,我猜测有两点。
一,认为这里lambda获得的对象的引用,但实际上两者对象都是在堆上开辟的(CPP第一个是栈上),除去一个会被回收,剩下的传递都是在循环内变量的引用,而不是对类的引用。无论是,即使是CPP的共享指针,它也需要你捕获这个指针的值。
二,认为这里完全把对象拷贝了,或者说,这里的捕获绑定,把lambda和对象一一绑定,在lambda的栈帧中,那个对象存在,但这样的认知是错误的。
有人提到move和rov ,还有rust的所有权,move等,但是我需要说明的是,在写代码中,如果你不关注这些,那么这些只能期望你的编程规范了,如果你在意它,也不用想rust那样move等等,代码是有明确的含义的,如果出现UB或者非你期望的东西,那么你需要回头看看,绝对是有地方发生 “俺寻思” ,但实际上,代码并非这样。
【 在 z16166 @ [CPlusPlus] 的大作中提到: 】
:
: 这个例子,chatgpt4分析的是延迟绑定问题,不是栈回收问题:
:
: 这段代码的主要问题在于 Python 的闭包延迟绑定。下面是具体的解析:
: 在这段代码中,我们看到在 main() 函数中创建了一个名为 a 的列表,用来存放一组返回 ai.p() 的 lambda 函数。然后在一层 for 循环里,把构建好的 lambda 函数分别放入列表 a 中。然后在下一层 for 循环中,尝试调用这些存储在列表中的函数。
#发自zSMTH@CDU.MP
--
FROM 113.143.107.*