CComPtr 和引用计数

CComPtr and reference count

我正在使用 CComPtr 类型的对象。但是我有一些内存泄漏问题。特别是,我有以下代码:

CComPtr<ID2D1Bitmap> bitmap = create_bitmap(bitmapSize);
auto n = count_ref((ID2D1Bitmap*)bitmap);

其中:

template<class Interface>
ULONG count_ref(Interface* pInterface) noexcept
{
    if (pInterface)
    {
        pInterface->AddRef();
        return pInterface->Release();
    }

    return 0;
}

并且:

ID2D1Bitmap* create_bitmap(const D2D1_SIZE_U& size)
{
    ID2D1Bitmap* bitmap;
    CreateBitmap(&bitmap);

    return bitmap;
}

我期望 n 的值等于 1 但实际上等于 2。为什么我的 CComPtr 的引用计数不是 1?

我是否正确使用了我的 CComPtr 对象?

当进程终止时,我得到以下内存泄漏:

An interface [072B1F50] was created but not released. Use 'dps 072B1F20' to view its allocation stack.
Object type: ID2D1Bitmap
    Device-dependent size: 1000 x 600
    Device-independent size: 1000.00 x 600.00
    Format: DXGI_FORMAT_B8G8R8A8_UNORM
    Alpha mode: D2D1_ALPHA_MODE_PREMULTIPLIED
    Outstanding reference count: 1

D2D DEBUG ERROR - Memory leaks detected.

当您从指针构造 CComPtr 时,它将共享该指针的所有权并增加引用计数。要在不增加引用计数的情况下获取指针的所有权,请使用 CComPtr::Attach method.

CComPtr<ID2D1Bitmap> bitmap;
bitmap.Attach(create_bitmap(bitmapSize));

有了 CComPtr,您很少需要使用原始接口指针类型。

你可以这样做,例如:

CComPtr<ID2D1Bitmap> create_bitmap(const D2D1_SIZE_U& size)
{
    CComPtr<ID2D1Bitmap> bitmap;
    CreateBitmap(&bitmap); // Declared as CreateBitmap(ID2D1Bitmap**);
    return bitmap;
}

CComPtr<ID2D1Bitmap> pBitmap = create_bitmap(...);
...

CComPtr class 将在您传递指针的过程中准确地管理引用:局部变量、return 值、新的局部值。在 Release 版本中优化编译器也会删除一些多余的 AddRef/Releases,因此您不必太担心它们。

这段代码有很多问题。

第一个问题是次要的,但它可能会导致很多误解。 AddRef()Release()not actually required to return any sensible values。所以他们 return 实际引用计数很好,但你不能每次都依赖它。所以基本上你的 count_ref() 功能是幼稚和不可靠的。

现在假设 Release() return 是真正的引用计数,显然 create_bitmap() return 是一个已经将其引用计数设置为 1 的对象。然后 CComPtr 调用 AddRef() 并且引用计数变为 2。然后当 CComPtr 超出范围时,它的析构函数调用 Release() 然后没有更多指向对象的指针并且它被泄露了。

后一个问题的解决方案是使用 CComPtr::Attach() 获取由 create_bitmap() 编辑的对象 return 的所有权,而无需再次调用 AddRef()

CComPtr<ID2D1Bitmap> bitmap;
bitmap.Attach(create_bitmap(bitmapSize));

这将使代码工作,但它不是很清楚并且可能比必要的更难维护。如果您负担得起更改 create_bitmap() 签名,那么最好将其更改为 returns CComPtr,如 中所建议的那样。这清楚地表明,调用者必须将对象的所有权授予任何看到函数签名的人,并且无需使用单独的调用 Attach().