2016-11-03 107 views
6

我一直在看香草萨特的CppCon 2016的谈话中,他举了一个例子around 37 minutes in,这样的事情:什么是C++中的回调地狱,为什么它会泄漏内存?

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

然后他说,

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

所以他说它被称为回调地狱,它会泄漏物体。 但我不太明白这段代码有什么问题,以及它为什么会泄漏。有人可以向我解释这一点吗?

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

+0

必须谈论内存所有权。由于C中没有垃圾收集器,所以很难知道何时/谁必须释放分配的对象。不过,'unique_ptr'可以提供帮助。 –

+0

如果它是公开的,请链接谈话。无论是否,请说出幻灯片#或演示文稿中的什么时间。 –

+0

@RawN:这不是无关紧要的,它只是没有足够的了解萨特博士所说的话,任何人都可以回答,而无需去查找和观看整个演讲。 –

回答

5

什么赫布萨特谈论的是循环引用。他促进了分层的系统,其中资源没有被流传下来的代码,“达到了”

1层 - 拥有从(下方)2层的资源和对象

二层技术 - 不能老是有对第1层对象的强烈参考

这确保了,依赖关系图不会获得圆圈。因此,如果第1层释放所有第2层对象,那么所有资源都会被破坏。 为什么这很重要很容易:如果obj a对obj b有强引用,并且obj b有强引用,则从C++ Std库进行的资源计数无法处理循环引用(无法重新计数)为了obj a,那么他们将永远不会被释放。

丑陋的真相是,如果圆圈可能通过不同作者的软件模块遍历多个引用,这也是一个问题。如果没有像图层一样的方案,你不能只看代码并且说“没有机会结束引用我从中调用的对象”。 Herb Sutter提出了一个约定,除非你知道实现,否则不应该调用可能使资源保持活跃的函数。

这并不是说你永远不应该这样做,但是如果你遵循一套规则,你可以在不知道系统深度的其他部分的情况下验证每层甚至每个文件的代码。否则,你必须找到函数(on_draw)可能采取的所有可能的路径,以查看是否可能导致循环依赖 - 并且如果可能触及的任何代码中有任何更改,则必须再次执行该操作!

在这种情况下,“回调地狱”是特别有问题的,因为它有点绕过类型系统(不可能只允许来自较低层的接口),并且回调可以做任何事情。

如果回调不保存对资源的引用,那么请使用普通指针代替,这明确指出调用者他并不需要担心泄漏。不是现在或将来。