有效的使用和设计COM智能指针——条款6:尽量以智能指针替换接口指针
如一个程序员要使用IHello接口的指针需要书写如下代码。
view plaincopy to clipboardprint?void SomeApp( IHello * pHello )
{
IHello* pCopy = pHello;
pCopy->AddRef();
OtherApp();
pCopy->Hello();
pCopy->Release();
}
void SomeApp( IHello * pHello )
{
IHello* pCopy = pHello;
pCopy->AddRef();
OtherApp();
pCopy->Hello();
pCopy->Release();
}
这样的代码看上去并没有太多问题,但是如果将异常考虑在内的话,上面代码就不那么乐观了。
假设OtherApp()中抛出了异常,那么pCopy->Release()将永远无法被执行到。COM组件无法释放,资源泄露也便产生了。有些公司规定代码中不允许出现异常,但即便是代码中不存在任何throw语句的情况下要消灭代码中所有的异常还是一件非常困难的事情。原因有如下:
1.你不能保证第三方类库中不抛出异常。或者你会话费相当大的时间来阅读文档已确定它不会抛出异常。
2.C++语言的某些操作默认会抛出异常。如默认的流操作,值传递中的动态申请内存,或者是动态的下转型dynamic_cast操作。
针对以上两点,我要补充说明的是消除异常是很困难的一件事情,而绝非不可能。但是这些问题总是在我们关注的时候被很好的解决,比如你可以要求不能使用流操作或者在new关键字前加nothrow来禁止这些异常的出现,甚至是更改编译器选项完全禁止异常。但若是出现了疏忽,异常便成了一个无法回避的问题。
或许你会继续争论将异常消灭的其他办法。但是事实你仅仅只是为了消灭吊异常所带来的副作用,而非其本生。在你拿出更好的办法前让我们先来看看智能指针如何解决这一问题:
view plaincopy to clipboardprint?void SomeApp( IHello * pHello )
{
CComPtr<IHello> spHello= pHello;
OtherApp();
spHello->Hello();
}
void SomeApp( IHello * pHello )
{
CComPtr<IHello> spHello= pHello;
OtherApp();
spHello->Hello();
}
CComPtr的用法很简单,它的详细介绍我们会在下一节中进行说明。而现在你只需要知道我们是怎么使用的即可。在这里,以IHello*为例,将程序中所有接口指针类型(除了参数),都使用CComPtr<IHello> 代替即可。即程序中除了参数之外,再也不要使用IHello*,全部以CComPtr<IHello>代替。或许这么做不会使给你程序带来太大的麻烦,但是他的收益却非常之大。
首先讨厌的AddRef()和Release()操作消失了。与其说消失,不如说智能指针帮我们在适当的时候处理了。这样做使得代码行数缩短,逻辑清晰的体现出来。
再回到异常问题上来,上述代码中若是OtherApp(),再抛出异常呢?此时的过程是,若OtherApp()抛出一个异常,则智能指针从所在函数中出栈,spHello被析构,CComPtr类型对象在析构过程中会自动调用Release()函数减少COM的引用计数,从而避免资源的泄漏。
这样看来,一切问题都优雅的解决了。或许你也不会再把心思画在消除异常所带来的副作用这类舍本求末的问题上了。
可能你已经初步感觉绝到智能指针带来了的一些便利之处,但如果这些优势还不足以说服你的话,或许下面这个例子会进一步改变你的想法:
view plaincopy to clipboardprint?IUnknown *PIUnknown = CreateInstance();
IX *pIX = NULL;
HRESULT hr = pIUnknown->QueryInte易做图ce(IID_IX, (void **)&pIX);
If (SUCCEEDED(hr))
{
pIX->Fx(); //这里开始主要的逻辑部分。
IX *pIX2 = pIX; //但引用计数操作却占据了这部分代码的一半。
pIX2->AddRef();
pIX2->Fx();
pIX2->Release();
pIX->Release();
}
IUnknown *PIUnknown = CreateInstance();
IX *pIX = NULL;
HRESULT hr = pIUnknown->QueryInte易做图ce(IID_IX, (void **)&pIX);
If (SUCCEEDED(hr))
{
pIX->Fx(); //这里开始主要的逻辑部分。
IX *pIX2 = pIX; //但引用计数操作却占据了这部分代码的一半。
pIX2->AddRef();
pIX2->Fx();
pIX2->Release();
pIX->Release();
}
上述代码中,为了满足引用计数的三条规则,pIX赋值给pIX2的时候调用了AddRef()。但实际上,由于pIX2的生命周期与pIX1相同,所以没有必要对pIX2使用AddRef()和Release()。这些冗余的代码使得代码的可读性大大降低。
同时,若程序中对接口指针赋值操作过多,也会导致由于程序员遗漏AddRef()与Release()操作,而造成灾难性的后果。并且这种错误的调试过程十分麻烦。
看看智能指针是如何解决这一问题的:
view plaincopy to clipboardprint?CComPtr<IX> spIX = NULL;
HRESULT hr = spIX.CoCreateInstance(CLSID_MYCOMPONENT);
If(SUCCEEDED(hr))
{
spIX->Fx(); //这里开始主要的逻辑部分。
CComPtr<IX> spIX2 = spIX; //有了智能指针,就只剩下逻辑了。:)
spIX2->Fx();
}
CComPtr<IX> spIX = NULL;
HRESULT hr = spIX.CoCreateInstance(CLSID_MYCOMPONENT);
If(SUCCEEDED(hr))
{
spIX->Fx(); //这里开始主要的逻辑部分。
CComPtr<IX> spIX2 = spIX; //有了智能指针,就只剩下逻辑了。:)
spIX2->Fx();
}
如果这还不够麻烦的话,那看看下面这个例子【5】。看完这个例子你可能会对于智能指针的使用更加渴望。
view plaincopy to clipboardprint?void f(void) {
IUnknown *rgpUnk[3];
HRESULT hr = GetObject(&rgpUnk[0]);
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk [1]); //为了使得代码简单这里用GetObject代替QueryInte易做图ce
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk[2]);
if (SUCCEEDED(hr)) {
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
rgpUnk[2]->Release();
}
rgpUnk[1]->Release();
}
rgpUnk[0]->Release();
}
}
void f(void) {
IUnknown *rgpUnk[3];
HRESULT hr = GetObject(&rgpUnk[0]);
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk [1]); //为了使得代码简单这里用GetObject代替QueryInte易做图ce
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk[2]);
if (SUCCEEDED(hr)) {
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
rgpUnk[2]->Release();
}
rgpUnk[1]->Release();
}
rgpUnk[0]->Release();
}
}
我并不觉得你能一眼看清楚上面代码的关键所在。只有当你一行一行读下来,你才会恍然大雾“原来只是为了调用UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2])这个函
补充:软件开发 , C语言 ,