> 文章列表 > 当对象的引用计数为零时

当对象的引用计数为零时

当对象的引用计数为零时

上一篇文章,我提到要避免对象的析构函数被调用两次,有一位读者声称:当对象第一次被构建的时候,它的引用计数应该为 0,在某些时候,例如调用 QueryInterface 的时候,它的 AddRef 方法应该被调用以增加其引用计数。

如果在构造一个对象时将它的引用计数设置为 0,你有点像在玩火柴。对于新手来说,当对象被创建的时候,它的引用计数不应该为 0,因为创建此对象的人拥有这个对象的一个引用。

请记住,对于 COM 对象来说,它的引用计数规则是:当一个函数产生一个引用(通常是一个接口指针)时,引用计数会增加。如果你将构造函数看做是一个函数的话,则它需要增加引用计数的值,来体现它创造了一个对象这样一个事实。

如果你更喜欢玩火柴,你最终可能会用如下代码烧到自己:

>> 请移步至 topomel.com 以查看图片 <<

请注意,在上面的代码中,我们将对象的引用计数初始化为 0。这使你处于与清理对象相同的“边缘区域”,而该对象引用计数为零,因此你会面临相同的问题:

>> 请移步至 topomel.com 以查看图片 <<

在销毁过程中保存自身的对象很可能在创建过程中加载自身。你遇到了完全相同的问题。对 IObjectWithSite::SetSite(this) 的调用会将对象的引用计数从 0 增加到 1,而对 IObjectWithSite::SetSite(NULL) 的调用会将其递减为零。当引用计数递减到零时,这将销毁对象,从而导致对象被 MyObject::Load 方法无意中销毁。

MyObject::Create 静态方法没有意识到这种情况已经发生,并继续调用 QueryInterface 方法以将指针返回给调用方,期望它将引用计数从 0 增加到 1。不幸的是,它正在对已经被摧毁的对象执行此操作。

当你使用引用计数为零的对象时,就会发生这种情况:当你放弃控制权时,它可能会消失。创建的对象应具有 1 的引用计数,而不是零。

ATL更喜欢玩火柴,在其对象构造中使用上述 MyObject::create 函数的道德等价物:

>> 请移步至 topomel.com 以查看图片 <<

ATL 会调用引用计数为零的 FinalConstruct 方法来构造一个 COM 对象。如果你知道这种方式的潜在危险,则可以使用
DECLARE_PROTECT_FINAL_CONSTRUCT 宏将 InternalFinalConstructAddRef 和 InternalFinalConstructRelease 方法更改为在调用 FinalConstruct 期间实际临时增加引用计数的版本,然后在 QueryInterface 调用之前将引用计数放回零(不破坏对象)。

它有效,但在我看来,它过于依赖程序员的警惕性。ATL 的默认设置是将选择权交给程序员,并依靠程序员“知道”FinalConstruct中可能发生危险的事情,并且有意识地要求
DECLARE_PROTECT_FINAL_CONSTRUCT。换句话说,它选择了危险的默认值,程序员必须明确要求安全版本。但是程序员有很多事情要做,强迫他们考虑在 FinalConstruct 方法中执行的每个操作的传递闭包的后果是一个不合理的要求。

考虑我们上面的例子。最初编写代码时,Load 方法可能要简单得多,如下图所示:

>> 请移步至 topomel.com 以查看图片 <<

直到一两个月后,才有人向加载和保存方法添加了站点支持。这个看似简单而孤立的更改,完全遵守了引用计数的 COM 规则,在对象创建和销毁代码路径中产生了连锁反应。如果在 FinalConstruct 和 Load 之间放置四个级别的函数调用,那么这种第四级调用器效应很容易被忽略。我怀疑这些非局部效应是代码缺陷的最重要来源之一。ATL很聪明,优化了一个增量和一个递减(编译器很可能可以自己优化出来),但作为回报,你得到了一盒”火柴盒”。

(我并不是要在这里挑剔ATL,它试图设计的又小又快,但代价是增加了复杂性,并且这种复杂性是很微妙的,难以一眼就能理解的)

总结

使用引用计数来管理对象的生命周期,我一直是比较抗拒的。我总是担心会发生一些令人意想不到的事情(比如发生了地震导致CPU上的一根引脚短路),导致了本该释放的对象因为引用计数的计算错误而没有得到释放,或者本不该释放的对象,因为引用计数而提前释放。
我不能将一个随时可能崩溃的程序交付给我的用户,绝不。
如果正在阅读本文的大大(你)有什么好的妙招,希望能对愚钝的我指教一二。

最后

Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《On objects with a reference count of zero》