c++中的回调地狱是什么?为什么它会泄漏内存?

What is callback hell in C++ and why will it leak memory?

本文关键字:为什么 泄漏 内存 是什么 回调 地狱 c++      更新时间:2023-10-16

我一直在看Herb Sutter的CppCon 2016演讲,他在演讲中给出了一个大约37分钟的例子,像这样:

void f(shared_ptr<T> & ptr)
{
    obj.on_draw([=]() { ... }
}

他接着说,

我听说它被称为回调地狱,在那里你注册一个回调和它有一个强所有者——它恰好是一个垃圾收集指针但它是一个强大的主人——但你永远不会摆脱它,它是只是永远存储在那里,现在对象永远不会消失。

所以他说这叫做回调地狱它会泄漏对象。但是我不太明白这段代码有什么问题,为什么会泄漏。有人能给我解释一下吗?

我看过stackoverflow上的其他答案,但它们似乎都是关于并发的。

Herb Sutter所说的是循环引用。他提倡分层系统,其中资源不会传递给"到达"的代码

Layer 1 -拥有来自Layer 2(及以下)的资源和对象

Layer 2 -不能有对Layer 1对象的强引用

这保证了依赖关系图不会变成圆形。所以如果Layer 1释放了所有的Layer 2对象,那么所有的资源都会被破坏。这一点很重要:c++ Std库的资源计数不能处理循环引用(没有reff计数可以),如果obj a对obj b有强引用,obj b对obj a有强引用,那么它们永远不会被释放。

丑陋的事实是,如果循环遍历多个引用,可能通过不同作者的软件模块,这也是一个问题。如果没有像层这样的方案,你就不能只是看着代码说"这不会引用我正在调用的对象"。Herb Sutter提出了一个约定,除非你知道实现,否则你永远不应该调用可能保持资源存活的函数。

这并不是说你永远不应该这样做,但是如果你遵循一组规则,你可以在不知道系统深度的情况下逐层甚至逐文件验证代码。否则,您必须找到函数(on_draw)可能采用的所有可能路径,以查看是否会导致循环依赖—如果可能触及的任何代码中有任何更改,那么您必须重新执行!

在这种情况下,"回调地狱"尤其有问题,因为它在某种程度上绕过了类型系统(不可能只允许来自低级别的接口),并且回调可以做任何事情。

如果回调不保存对资源的引用,则使用普通指针代替,这显式地向调用者声明他不需要担心泄漏。现在不行,将来也不行。