有效的使用和设计COM智能指针 ——条款13:必须提前释放COM组件时,别妄想智能指针帮你完成
2011-09-16 10:49
393 查看
条款13:必须提前释放COM组件时,别妄想智能指针帮你完成
更多条款请前往原文出处:http://blog.csdn.net/liuchang5有了智能指针,或许你不会想到要自己手动释放或者增加引用计数了。那么请欣赏一下下面这个函数:
void InSomewhere(IHello *pHello) { CComPtr<IHello> spHello = pHello; spHello->DoSomething(); HRESULT hr =CoCreateInstance( CLSID_HELLO NULL, CLSCTX_INPROC_SERVER, IID_IHELLO, (void **)&spHello //这里可能出现资源泄漏 ); assert(SUCCEED(hr)); spHello->DoOtherthing(); }
你认真的核对了一下IID和指针的类型,发现没有问题。并且CoCreateInstance后面这个assert让你对他的行为信心十足。
但是问题是spHello原来是有所指的。他会释放掉相应接口的引用计数吗?答案是未定义。
对于spHello在非空情况下的取地址操作,在不同智能指针中定义不尽相同。对CComPtr来说,他会在DEBUG的时候出发一个断言,而在RELEASE版本就悄悄的让资源泄漏了。而对于_com_ptr_t而言,无论是在RELASE还是DEBUG版本中它都会偷偷的先释放掉原油资源,而不给外界任何关于这种危险操作的提示。
知道了原因可能你不会太期望用取地址符这种危险的操作了。你可能想到了用前面“条款11:以类型安全的方式创资源和查询接口”所讲述的内容。看看下面这个修改版会不会存在问题。
void InSomewhere(IHello *pHello) { CComPtr<IHello> spHello = pHello; spHello->DoSomething(); spHello.CoCreateInstance( CLSID_HELLO );//这里可能出现资源泄漏 assert(spHello); spHello->DoOtherthing(); }
问题还是发生了,因为CoCreateInstance仍然只是在DEBUG版本中spHello非空的情况下做了一个断言。而RELEASE中资源就泄漏了。而对于_com_ptr_t来说,仍然是悄悄的先帮你把资源给释放掉。
因此最稳妥的应对策略是,“必须提前释放COM组件时,别妄想智能指针帮你完成”。
void InSomewhere(IHello *pHello) { CComPtr<IHello> spHello = pHello; spHello->DoSomething(); spHello = NULL; //或者spHello.Release(); 提前释放资源 spHello.CoCreateInstance( CLSID_HELLO ); assert(spHello); spHello->DoOtherthing(); }
OK,问题解决了。
更有一些情况下我们不知觉的导致了内存泄漏,原因同样是由于你没有手动释放COM组件。假设我们设计了这么一个容器来存放智能指针如下:
template<typename T> class MyStack { public: MyStack(int nCapacity){ m_pArray = new T[nCapacity](); m_nCapacity = nCapacity; m_nTop= 0; }; ~MyStack(){ delete[] m_pArray; }; void push(T Item){ assert(m_nTop < m_nCapacity); m_pArray[m_nTop++] = Item; } T pop(){ assert(m_nTop > 0); return m_pArray[--m_nTop]; } MyStack(const MyStack<T>& otherArray) { //deep copy it }; private: T *m_pArray; int m_nTop; int m_nCapacity; };
而使用这个MyStack的过程是如下这样的:
MyStack<CComPtr<ICalculator>> g_myStack(1000); void func() { for (int i=0; i<500; i++) { CComPtr<ICalculator> spCalculator = NULL; spCalculator.CoCreateInstance(CLSID_CALCULATOR); g_myStack.push(spCalculator); } ... for (int i=0; i<500; i++) { CComPtr<ICalculator> spCalculator = g_myStack.pop(); spCalculator->DoSomething(); } }
500个CoCreateInstance()这个操作或许会让你觉得有点疯狂。但我仅仅是想让你知道内存泄漏是如何潜在的发生的。
其原因是什么? 智能指针无法在这种情况下自动帮我们释放掉相应的资源,首先他在全局空间上。如果想等待~MyStack()这个析构函数被调用可能需要等到程序结束。而在pop后我们不应当继续在MyStack中保存智能指针对接口的引用。试想,若之后这个MyStack中的元素永远达不到500个。那么,MyStack后半部分所持有的资源永远无法被释放掉。
因此必须手动释放COM组件时,别妄想智能指针帮你完成,稍微改写一下pop()函数,资源泄漏问题解决了。
template<typename T> class MyStack { public: .... T pop(){ assert(m_nTop > 0); T tempArray = m_pArray[--m_nTop]; m_pArray = NULL; //或者用 m_pArray.Release(); return tempArray; } private: .... };
谨记在心,智能指针只是辅助我们管理内存的手段。必须提前释放COM资源时,别妄想智能指针帮你完成。这一节可以结束了。
相关文章推荐
- 有效的使用和设计COM智能指针——条款23:为例外条件准备应对策略。
- 有效的使用和设计COM智能指针——条款15:以原则中的优先级作为取舍的依据
- 有效的使用和设计COM智能指针——条款24:努力使得接口容易被使用而不易被误用。
- 有效的使用和设计COM智能指针——条款11:以类型安全的方式创建资源和查询接口
- 有效的使用和设计COM智能指针——条款12:必要时使用attach() 和 detach()调整引用计数
- 有效的使用和设计COM智能指针 ——条款16:智能指针的引入不能违反COM引用计数规则
- 有效的使用和设计COM智能指针——条款1:智能指针之前世今生
- 有效的使用和设计COM智能指针—条款4:理解ATL的CComPtr提倡简单
- 有效的使用和设计COM智能指针——条款2:引用计数的是与非
- 有效的使用和设计COM智能指针——条款6:尽量以智能指针替换接口指针
- 有效的使用和设计COM智能指针——条款17:重载运算符时应当符合C/C++约定
- 有效的使用和设计COM智能指针——条款18:重载运算符不应当扭曲其语义
- 有效的使用和设计COM智能指针——条款3:按照功能和实现原理选择合适的智能指针
- 有效的使用和设计COM智能指针——条款19:在接口完满的前提下使之最小化。
- 有效的使用和设计COM智能指针——条款25:思考兼容取地址操作符带来的若干问题
- 有效的使用和设计COM智能指针——条款4:理解ATL的CComPtr提倡简单,高效
- 有效的使用和设计COM智能指针——条款26:自动查询接口带来方便同时也潜藏危机
- 有效的使用和设计COM智能指针 ——条款5:了解_com_ptr_t 设计背后的历史原因
- 有效的使用和设计COM智能指针 条款8:条款9:尽可能不将智能指针放置于堆上
- 有效的使用和设计COM智能指针——条款20:安全的覆盖掉C++默默为我们编写的函数