我将以只有运行时的开销作为开始,它是通过实现类模板介绍的:
protected: unsigned long m_references = 1; Implements() noexcept = default; virtual ~Implements() noexcept {}默认构造函数并不是真正的开销所在,它只是简单的确保最终的构造函数--它将初始化引用计数--为protected而不是public的.引用计数和虚构造函数都是protected的.让派生类访问引用计数,是为了允许更复杂的类组合.大多数类可以简单的忽略它,但是需要注意的是,我正初始化引用计数为1.这和通常建议初始化引用计数为0,形成鲜明的对比,因为此时并没有处理引用.这个方式在ATL中非常流行,明显受到Don Box的COM本质论的影响,但是这是非常有问题的,ATL的源代码的研究可以作为佐证.开始于这个假设,即引用的所有权将会立即由调用者获得,或者依附于一个提供更少错误构造处理的智能指针.
虚析构函数提供了很大的便利性,它允许实现类模板实现引用计数,而不是强制实现类本身来提供实现.另一个选项,是使用奇特的递归模板模式(Curiously Recurring Template Pattern)来避免使用虚函数.通常我会选择这个方法,但是它会稍微增加抽象的复杂性,同时,因为COM类本身有一个vtable,所以这里也没有什么理由去避免使用虚函数.有了这些基本类型之后,在实现类模板中实现AddRef和Release将会变得非常简单.首先,AddRef方法可以简单的使用InterlockedIncrement来增加引用计数:
virtual unsigned long __stdcall AddRef() noexcept override { return InterlockedIncrement(&m_references); }这不言自明.不要想出某些复杂的方法,通过使用C++的加减操作符来有条件的替换InterlockedIncrement和InterlockedDecrement函数.ATL通过极大的增加复杂性去做这个尝试.如果你考虑效率,宁可为避免调用AddRef和Release产生谬误而多花心思.同样的,现代C++增加了对move语义的支持,以及增加转移引用所有权的能力.现在,Release方法只是略显复杂:
virtual unsigned long __stdcall Release() noexcept override { unsigned long const remaining = InterlockedDecrement(&m_references); if (0 == remaining) { delete this; } return remaining; }引用计数减少后,结果被赋值给临时变量.这很重要,因为结果需要返回.但是如果对象销毁了,引用此对象的成员变量就是非法的了.假定没有其它未处理的引用,这个对象就通过前面说到的虚析构函数删除了.这就是引用计数的结论,实现类Hen仍然和之前的一样简单:
class Hen : public Implements<IHen, IHen2> { };现在,到了想象一下QueryInterface的奇妙世界的时间了。实现IUnknown方法是一个很重要的实践。在我的Pluralsight课程中,我广泛的实现了它。你可以在Don Box编写的<<COM本质论>>(Addison-Wesley Professional,1998)一书中,阅读关于实现你自己的IUnknown的奇妙的和不可思议的方法。需要注意的是,虽然这是一本关于COM的优秀书籍,但是它是基于C++98的,并没有呈现出任何现代C++的特征。为了节省时间,我假定你已经熟悉了QueryInterface的实现过程,并集中于如何用现代C++实现它。下面是虚函数本身:
virtual HRESULT __stdcall QueryInterface( GUID const & id, void ** object) noexcept override { }给定一个GUID用来标识一个特别的接口之后,QueryInterface应该来决定一个对象是否实现了需要的接口。如果实现了,它必须减少这个对象的引用计数,同时通过外部参数来返回所需的接口指针。如果没有实现,它必须返回一个空指针。因此,我将以一个粗略的轮廓来作为开始:
*object = // Find interface somehow if (nullptr == *object) { return E_NOINTERFACE; } static_cast<::IUnknown *>(*object)->AddRef(); return S_OK;QueryInterface首先会尝试设法查找所需的接口。如果接口受不支持,则返回E_NOINTERFACE错误码。请注意,我是如何按照要求处理接口指针不支持的情况。你应该把QueryInterface接口看作是二元的操作。它要么成功找到所需的接口,要么查找失败。不要尝试发挥创造性,只需要依据条件响应即可。尽管COM规范有一些限制项,但是大多数消费者都会简单的假定接口不受支持,而不管你会返回何种错误码。在你的实现中的任何错误,都毫无疑问的会导致你陷入调试的深渊。QueryInterface是非常基础的,不能胡乱对待。最后,AddRef由接口指针再次调用,用来支持某种极少的而又允许的类组合场景。这些不受实现类模板的显式支持,但是我情愿在这里做一个表率。重要的是,记住引用计数操作是面向接口的,而不是面向对象的。你不能 简单的,在属于一个对象的任意接口上面,调用AddRef或者Release。你必须依赖COM规则来管理对象,否则你会冒险引入以不可思议��方式崩溃的非法代码。