条款16:智能指针的引入不能违反COM引用计数规则
我们之前已经看过类似的函数。它是严格遵照了引用计数的规则而编写的:
IView* GetView(int nIndex)
{
IView* pView = m_Views[nIndex];
pView->AddRef(); //引用计数被增加了一次。
return pView;
}
嗯~ 这样没错,但是你可能觉得使用起来就不太方便了:
void UseView(int nIndex)
{
CComPtr<IView> spView = NULL;
spView.Attach(GetView(0)); //这里需要用Attach将其绑定到智能指针之上
spView->UserIt();
}
你可能会做这样的考虑。如果能想办法改造一下GetView,之后Attach和Detach操作就能从智能指针的实现中剔除出去。那样智能指针的设计和使用都将得到简化。
CComPtr<IView> GetView(int nIndex)
{
CComPtr<IView> pView = m_Views[nIndex];
return pView;
}
哦~ 貌似这样看上去很好。但她却实属一个糟糕的设计。如果使用他的人如此操作这一指针,那你是幸运的:
CComPtr<IView> spIView = NULL;
spIView = GetView();
spIView->DoSomething();
嗯,一切都正常的进行着。并且你自己揣摩着智能指针拷贝时候的引用计数。发现它没有混乱,而且井井有条。但这也仅仅是在你完全使用智能指针对资源进行管理的情况下。如果使用者没有在函数返回处用智能之中接收接口指针,而使用如下这样的方式,那么就不容乐观了:
IView *pIView = NULL;
pIView = GetView();
pIView->DoSomething();
哦~ 可以想象,这个时候由于智能指针出栈,引用计数已经归0。那么pIView已经指向了一个被释放的COM组件。
或许你还想亡羊补牢,做一些补救措施,让他能继续工作:
IView *pIView = NULL;
pIView = GetView();
pIView ->AddRef(); //这种挽留已经为时过晚
pIView->DoSomething();
这已经为时过晚了,试想如果GetView中传出的接口引用计数为1,那么当返回值被析构,引用计数归0,此时组件已经被释放得干干净净了。但之后你却调用了一次AddRef()试图对引用计数的递减做一些挽留。这样一来迎接你的只会是是内存错误。
让我们总结一下上面这些糟糕的代码,看看他们有哪些错误之处。首先,它仍然违反了条款3中“别让智能指针存在于接口的参数和返回值中”,这再一次体现了由于不遵守这个重要条款而带来的副作用。其次他企图去改变COM引用技术的规则:在GetView()之后再次调用了AddRef()。幸好犯罪未遂。
那之所以有人想这么做的目的是什么呢?额~ 我想他可能认为诸如此类的方法可以将Attach和Detach这样的函数从智能指针中剔除出去,这样能使得智能指针的设计和使用更加的优雅。
但请谨记在心:设计的智能指针不能违反COM接口的引用计数规则。纵使你有办法让代码完全使用智能指针对资源进行管理,而不出现COM引用计数遗留下来的若干问题。但也请你照顾接口指针的引用技术规则,他会让你的智能指针被更多的地方所使用。