我一直在观看Herb Sutter的CppCon 2016演讲,其中他给出了一个例子around 37 minutes in,如下所示:
void f(shared_ptr<T> & ptr)
{
obj.on_draw([=]() { ... }
}
然后他说,
我听说它叫做回调地狱,在那里注册一个回调和 它有一个强大的所有者 - 它恰好是一个垃圾收集指针 但它是一个强大的所有者 - 但是你永远不会摆脱它而且它是 只是永远存储在那里,现在物体永远不会消失。
所以他说它被称为回调地狱,它会泄漏对象。 但是我不太明白这段代码有什么问题以及为什么它会泄漏。有人可以向我解释一下吗?
我在stackoverflow上看了别人的答案,但他们似乎都是关于并发的。
答案 0 :(得分:5)
Herb Sutter所说的是循环引用。他推出了一个分层系统,资源不会传递给“达到”的代码
第1层 - 拥有第2层(及以下)的资源和对象
第2层 - 不能强烈引用第1层对象
这确保了依赖关系图不会得到圆圈。因此,如果第1层释放所有第2层对象,则所有资源都将被销毁。 为什么这很重要很容易:从C ++ Std库中计算的资源不能处理循环引用(没有引用计数可以),如果obj a对obj b有强引用而obj b有强引用obj a,然后他们永远不会被释放。
丑陋的理由是,如果圆圈可能通过不同作者的软件模块遍历多个引用,这也是一个问题。如果没有类似图层的方案,你就不能只看代码并说“没有机会最终引用我正在调用的对象”。 Herb Sutter提出了一个约定,除非你知道实现,否则你永远不应该调用一个可以保持资源存活的函数。
这并不是说你永远不应该这样做,但是如果你遵循一套规则,你可以在不知道系统其他部分的情况下验证每层甚至每个文件的代码。否则,您必须找到函数(on_draw)可能采用的所有可能路径,以查看是否可能产生循环依赖关系 - 如果任何可能触及的代码发生任何变化,那么您必须再次执行此操作!
在那种情况下,“回调地狱”特别成问题,因为它有点绕过类型系统(不太可能只允许来自较低级别的接口),并且回调可以做任何事情。
如果回调没有保存对资源的引用,那么使用普通指针代替,这明确地向调用者说明他不需要担心泄漏。不是现在或将来。