在QueryInterface()实现中调用AddRef()的正确方法

我发现QueryInterface()一些实现模式如下所示:

 // Inside some COM object implementation ... virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppv) { *ppv = /* Find interface ... */ if (*ppv == nullptr) return E_NOINTERFACE; static_cast<IUnknown *>(*ppv)->AddRef(); // ### return S_OK; } 

感兴趣的线是用// ###注释标记的线。

IUnknown static_cast -pointer上调用AddRef()是否真的有必要? 还是只是没用的样板代码?
换句话说,一个简单的AddRef()调用(即this->AddRef() )就好了吗? 如果没有,为什么?

当然,你通常只有一个 AddRef()实现,所以你怎么称呼它并不重要。 注意代码使用ppv的方式是可能的灵感,它是无类型的(void **),所以需要cast。 也许撕掉会让你做不同的事情。

主要原因是拆卸接口指针(例如对于很少使用的接口)和可聚合的对象(mixin的COM等价物,或多或少)。

在这些情况下(当被要求聚合的IID时,是一个撕裂或聚合器), ppv不是一个接口指针,指向相同的ref计数的C ++对象。 因此,如果您也想支持这些情况,则该代码是必需的。

通过调用this->AddRef ,可能会获得一些简单性或类型安全性,但代价是不支持未由相同C ++对象明确实现的接口。


PS:与大多数书籍和文档所说的相反,在我看来:

  1. 聚合与使用mixin更相似,而不是继承或组合;
  2. 聚合实际上是(缓存的)可拆卸接口指针的一种特殊情况,而不是一种特殊的构图情况。

这是我的思路:

  1. 当你继承时,你通常有机会覆盖(虚拟)方法,由于直接的方法调用,这不是聚合的情况; 当你使用合成时,你可能必须包装入站对象,以免让内部对象泄漏它的身份给一个给定的对象(例如,内部对象可能将自己传递给入站对象的某种方法),而聚合也意味着共享身份有两套IUnknown的可聚合物体方法,因此完全没有这个特殊问题;
  2. 撕裂有它自己的一生,而可聚合的对象与外部对象共享其生命。 否则,可能只在需要时创建,尽管聚集器通常在创建它们时立即创建可聚合对象。

返回的接口不需要是实现QueryInterface的类的基类。