有效的使用和设计COM智能指针——条款17:重载运算符时应当符合C/C++约定

26 篇文章 0 订阅
25 篇文章 1 订阅

条款17:重载运算符时应当符合C/C++约定

更多条款请前往原文出处:http://blog.csdn.net/liuchang5

假设我们用了第三方的stack模版类,他的pop函数形如下面这个样子:

template<class T> 
void Stack<T>::Pop( T& result )
{
  if( vused_ == 0)
  {
    throw "pop from empty stack";  
   }
  else
  {
    result = v_[--vused_];
  }
}

可惜的是这个函数并非出自大师只手,它是异常请全的吗?只能说勉强够格,而条件是T类型的赋值运算符应当遵守C/C++的约定,而不抛出异常。试想如果赋值运算符抛出了异常,那么stackvused已经被递减了,而操作却没有完成。此时对象的状态就产生了不一致【13】。

但这个函数确实无可厚非,他的异常安全是建立在C/C++的约定之上 。如果你的T类型让赋值运算符抛出了一个异常,那么应该说责任在你——因为你违反了约定。

C/C++说不要让赋值运算符抛出一个异常,并且C++默认为我们准备的赋值运算,如果我们编写一个智能指针,那么他应该是这样的:

MySmartPtr<T>& MySmartPtr::operator=(const MySmartPtr<T>& mySmartPtr)
{
   ... //若干操作,但不应该有异常抛出
}

如果它确实不会抛出异常,那么他是符合C/C++约定的。但若是他不小心抛出了一个异常,或者是他下层调用的函数抛出异常呢?那么上述Stack模版类就沦落到了异常不安全的境地。

很明显他不是很受欢迎,因此我们或许会对他做若干改进。先给他加上一个throw()关键字,确保他不会抛出异常。但那或许还不够,因为他只是简单的让程序终止掉,我们希望对异常进行及时的处理,以便及时发现错误。改进后的方案是如下形式:

MySmartPtr<T>& MySmartPtr::operator=(const MySmartPtr<T>& mySmartPtr) throw()
{
   try{
   ... //若干操作,但不应该有异常抛出
   }catch(...){
    ...
    std:abort();
   }
   return *this;
}

但我们会发现CComPtr在做这个操作之时却做了一个因为常规的举动,我们从他的函数声明上观察一下:

T* operator=(_In_ const CComPtr<T>& lp) throw()

它再次违反C/C++的约定!因为他返回的并不是一个自身类型的引用!而CComPtr的初衷仅仅是让智能指针表现得更像一个指针。你可能会说这样没有什么太多害处。但下面的代码可能会让你的程序表现可能和你预期的一样,但在某些“不幸的情况”下程序崩溃了:

func(IUnknown *pIUnknown)
{
CComPtr spIUnknown = NULL;
m_pI = spIUnknown = pIUnknown;  //这种语法是C/C++承认的
DoSomething();
}
//之后成员属性m_pI可能会在其他地方调用

你可能会问“什么是‘不幸的情况’?”。设想一下如果m_pI是个接口指针。那么引用计数则少增加了一次,程序可能崩溃。若m_pI是智能指针,则你就很幸运了。

解决这一问题的方式仍然是遵守C/C++约定,将CComPtr改成如下方式的声明就可以了:

CComPtr<T>& operator=(_In_ const CComPtr<T>& lp) throw()

问题解决了。我们在看看_com_ptr_t,它在这里表现得并不老实,虽然他返回的类型是本类型的引用,但他可能由于接口查询失败而抛出一个异常。幸运的是他给赋值运算符添加上了thorw()关键字,这或许能让程序幸运的崩溃掉,而不让你陷入异常不安全的困境。他的声明是如下这样的:

    _com_ptr_t& operator=(const _com_ptr_t& cp) throw()

最后我们回到开头的部分,给出一个让Stack在不符合约定的情况下仍然异常安全的版本,这个版本来自于Exceptional C++。或许它并不能指导我们更好的编写一个通用的智能指针。但他却能让我们在异常安全问题上得到某些启示——异常抛出对象状态不应改变:

template<class T> 
void Stack<T>::Pop( T& result )
{
  if( vused_ == 0)
  {
    throw "pop from empty stack";
  }
  else
  {
    result = v_[vused_-1];
    --vused_;
  }
}

最后请谨记,让赋值运算符符合语言约定是值得的,他能让你的智能指针更少的背误用。即便遵守语言约定让你的代码出现了一些副作用,我们也可以认为,这些副作用是语言本生所产生的。事实上C/C++本生并不完美。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值