生命控制
客户不应直接控制组件的生命期
原因:当使用完一个接口而仍然使用另一个接口时,是不能将组件释放掉的。由于很难知道两个接口指针是否指向同一组件,因此决定何时可以安全的释放一个组件将是极为复杂的。得知两个接口指针是否是指向同一对象的唯一方法是查询这两个接口的Iunknown,然后对结果进行比较。当程序越来越复杂时,决定何时可以释放一个组件是极为复杂的。
我们可以通过组件何时需要使用它的某个接口以及何时使用完接口,而不是直接将接口删除。对组件的释放可以由组件在客户使用完其各个接口之后自己完成。IUnknown的两个成员函数AddRef和Release的作用就是给客户提供一种让它指示何时处理完一个接口的手段。
引用计数简介
AddRef和Release实现的是一种名为引用计数的内存管理计数。引用计数是使用组件能够自己将自己删除的最简单同时也是效率最高的方法。COM组件将维护一个称作是引用计数的数值。当客户从组件取得一个接口时,此引用计数将增1。当客户使用完某个接口后,组件的引用计数将减1。当引用计数值为0时,组件即可将自己从内存中删除。当创建某个已有接口的另外一个引用时,客户也将会增大相应组件的引用计数值。
为了正确的使用引用计数,读者需要了解一下规则:
- 1. 在返回之前调用AddRef。对于那些返回接口指针的函数,在返回之前应用相应的指针调用AddRef。这些函数包括QueryInterface及CreateInstance。这样当客户从这种函数得到一个接口后,它将无需调用AddRef。
- 2. 使用完接口调用Release。在使用完某个接口之后应调用此接口的Release函数。
- 3. 在赋值之后调用AddRef。在将一个接口指针赋给另外一个接口指针时,应调用AddRef。换句话说,在建立接口的另外一个应用之后应增加相应组件的应用计数。
<智能指针类可以把引用计数完全封装起来>
引用观点:
1 C++程序员可以精确控制对象释放的时机,这正是C++的长处。如果非要在C++里专门开一个线程来搞GC,那不如不用C++。
2 可以肯定的讲解决引用计数引起的问题是很痛苦的。因为在实际的程序中,很难决定某些没有加上的AddRef及Release调用到底是优化还是程序中的错误。
3用一个线程来定期扫描所有对象,无论从空间还是时间上都是浪费,如果一个对象自己能记住自己被引用了多少次,它就没有必要让另一个管理器来管理它。
引用计数的优点:
- 1. Deterministic Finalization. 当一个对象的引用计数到零时,线程执行的下一个操作是销毁对象和其他使用的资源。
- 2. Resource Sharing. 引用计数将对象的引用安全的传递到程序的其他部分。
- 3. Lifetime Encapulation. 对象负责维护自身引用计数器。
引用计数的缺点:
- 1. Circular Reference. 如果对象A包含了对对象B的引用,而对象B又包含了对对象A的引用,在这种情况下就会出现循环调用。
- 2. Thread Safety. 多个线程共享一个对象将是十分复杂的问题。
<正是由于以上两个缺点使得引用计数不太理想>
引用计数接口
在客户看来,引用计数是处于接口级上而不是组件级上的,每一个接口都有一个引用计数。组件可以对其每一个接口分别维护一个引用计数,也可以对整个组件维护单个的引用计数。
如果每一个接口分别维护一个引用计数,那就意味着客户应该对它将要使用的那个指针调用AddRef,而不是其他的什么指针。对于使用完了指针,客户应调用其Release。
使用单独维护一个引用计数主要原因是:使程序调试更为方便;另外一个原因是支持资源的按需获取。