如何让std::shared_ptr持有IUnknown对象

C++11新增的智能指针std::shared_ptr使用引用计数来管理对象的生命周期,而COM提供的IUnknown接口也使用引用计数来管理自身的生命周期。理论上来说,在同一个环境中不应该同时使用两套引用计数方案,不然会造成混乱,带来很多麻烦。然而实际上,需要同时使用这两种方案的情况并不少见,有时的确需要用std::shared_ptr来持有COM对象。虽然这种做法看上去丑陋,并且是可以用别的方法来避免的,但本文不讨论这些方面,只聚焦于问题的本身。

得益于std::shared_ptr提供的灵活性,持有COM对象是十分简单的。在默认情况下,当std::shared_ptr的引用计数达到0时,它会调用delete操作符来销毁持有的对象。然而在构造std::shared_ptr的时候,可以传入一个自定义的删除器,来改变它默认的删除行为。删除器可以是函数、仿函数或者lambda表达式等可调用的函数对象。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <memory>

int main() {

auto deletor = [](int* p) {
std::cout << "deleting " << p << std::endl;
delete p;
};

std::shared_ptr<int> i1(new int(), deletor);

std::shared_ptr<int> i2 = i1;
i1.reset();
}

上面的代码用一个lambda表达式作为变量i1的删除器,该删除器在销毁int之前会打印出一行消息。删除器会随着std::shared_ptr一同拷贝或赋值,所以不必担心删除器会丢失。在上面的代码中,变量i2接管了删除器,在程序退出之前依然会打印出那条消息。

显然,对于COM对象来说,只要在删除器里调用Release方法来替换delete操作符即可。下面是将IUnknown对象转成std::shared_ptr的函数:

1
2
3
4
5
std::shared_ptr<IUnknown> ConvertToSharedPtr(IUnknown* unknown) {

unknown->AddRef();
return std::shared_ptr<IUnknown>(unknown, [](IUnknown* p) { p->Release(); });
}

在将COM对象传递给std::shared_ptr之前务必要调用AddRef方法增加它的引用计数。这样一来,在同一个对象上存在着两个引用计数:IUnknown的引用计数是主要的,只有当这个引用计数达到0时,对象才会被销毁;std::shared_ptr的引用计数是次要的,当这个引用计数达到0时,只会把IUnknown的引用计数减一。两者和谐共存,不会相互影响。