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

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

本文关键字:AddRef 方法 QueryInterface 实现 调用      更新时间:2023-10-16

我发现了一些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())没事就好吗?如果没有,为什么?

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

主要原因是撕掉接口指针(例如,对于很少使用的接口)和可聚合对象(或多或少相当于 mixins)。

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

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


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

  1. 聚合更类似于使用混合,而不是继承或组合;
  2. 聚合实际上是(缓存的)撕裂接口指针的特殊情况,而不是组合的特殊情况。

这是我的想法:

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

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