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

Proper way of calling AddRef() inside QueryInterface() implementation

我找到了一些 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 指针上调用 AddRef() 真的有必要吗?或者它只是无用的样板代码?
换句话说,一个简单的 AddRef() 调用(即 this->AddRef())就可以了吗?如果不是,为什么?

返回的接口不必是 class 实现 QueryInterface 的基础 class。

当然,您通常只有 一个 AddRef() 实现,因此您如何调用它并不重要。请注意代码使用 ppv 的方式是可能的灵感来源,它是无类型的 (void**),因此需要进行强制转换。也许撕掉会让你以不同的方式做这件事。

主要原因是分离接口指针(例如,对于很少使用的接口)和可聚合对象(或多或少等同于 COM 的 mixins)。

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

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


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

  1. 与继承或组合相比,聚合更类似于使用混合;
  2. 聚合实际上是(缓存的)分离接口指针的特例,而不是组合的特例。

这是我的思路:

  1. 当你继承时,你通常有机会重写(虚拟)方法,由于直接方法调用,聚合不会出现这种情况;当您使用组合时,您可能必须包装入站对象,以免内部对象将其身份泄露给给定对象(例如,内部对象可能将其自身传递给入站对象的某些方法),而聚合也意味着通过以下方式共享身份在可聚合对象上有两组 IUnknown 的方法,因此根本没有这个特殊问题;
  2. 分离对象有自己的生命周期,而可聚合对象与外部对象共享生命周期。否则,可能仅在需要时才创建任何一个,尽管聚合器通常会在创建可聚合对象后立即创建它们。