条款23:为例外条件准备应对策略。
如果我们希望所设计出来的智能指针更加的智能,我们可能会想在他的构造函数上做文章,让他能在构造之时通过传入一个接口来构造其本身(类似_com_ptr_t的设计)。当然这个时候,传入的接口可能并非与智能指针指向的类型相同,那么可能你会编写如下这么一个函数来进行一个查询操作:
template <class T>
class CMyComSmartPtr
{
public:
template<typename _InterfaceType> _com_ptr_t(_InterfaceType* p)
: m_pInterface(NULL)
{
HRESULT hr = _QueryInterface(p);//这里进行接口查询
if (FAILED(hr) && (hr != E_NOINTERFACE)) {
... //进行错误处理
}
}
...
}
他看上去很华丽,但是它却潜藏危机,如果你想了解他的危险可以查看一下条款26。但如果你仍然坚持要这样做,并认为这种自动转换所带来的便利可以掩盖他所带来的负面影响的话,我们可能要探讨一下为实现这一功能所要考虑的细节问题了。
上述代码中,只要_InterfaceType能够查询到T类型的接口,那这种查询转换可以顺利完成。但如果_InterfaceType不能查询到T类型呢?
如果你是个细心的人,你会发现_InterfaceType 是个纠结的类型,他并没有说不可能是当前类型,或者是一个不从IUnknwon接口继承下来的类型。
试想一下如果执行下面这样的语句是否会增加额外的开销?
Func(ICalculator pCalculator)
{
CMyComSmartPtr<ICalculator> spCalculator = pCalculator;
...
}
每次都进行接口查询,确实大可不必有这样的开销。而对于这种类似于同类型的问题,我们应该有个特化函数专门处理。他看上去应该是这样的:
template<> CMyComSmartPtr(Interface* pInterface) throw()
: m_pInterface(pInterface)
{
_AddRef();
}
进过一次修改,效率提升了。但这样也仅仅是看上去没有什么问题。试着执行以下下面段代码。
CMyComSmartPtr<ICalculator> spICalculator = NULL;//编译无法通过
....
哦~ 看来我们忘记考虑初始化赋值为空的情况了。因为_InterfaceType*并没有要求指针是从IUnknown继承而来。于是得再加入一个重载,应该能解决这一问题:
CMyComSmartPtr(int null) : m_pInterface(NULL)
{
assert(null)
}
如此一来,智能指针的行为算是正常许多了。但是这一功能付出的代价是一大段的特化函数和重载以及一大顿绕不清楚的逻辑(看看_com_ptr_t的16个构造函数可能会让你明白这些特化情况有多么负载)。
而当构造失败,很多时候你并不太方便去获取他的错误。因为这样的构造函数无法为你准备相应的错误返回值,因此它只能选择抛出异常这种形式。而对于异常安全和禁止异常的代码中,他成了个麻烦事。
自动查询接口,真不是一个好特性。它不仅让智能指针容易被误用,而且让它的内部逻辑和特化情况异常复杂。通过本条款,我们不仅应当有意识的为例外情况做特化处理。