C++ trick之重载类型转换操作符 “orthodox”技术

转自: http://sandy-sp.com/blog/article.asp?id=20

 

C++ trick之重载类型转换操作符

C++很强大,许多玩意都可以自定义,比如类型转换操作符。
但我这里要扯的是,它的trick或trap,也就是在许多情况下会有一些陷阱,如果不注意而滥用,则很容易出现一些莫名其妙的问题。

用过MFC的都知道,CString对象在许多情况下都可以当作LPCTSTR类型(const char* 或者const wchar_t*)来使用:

C++ 代码:
CString str =  "1234";
int a = atoi(str);  // ok, auto convert CString to const char*
MessageBox(str);  // ok

这样当在一个需要LPCTSTR类型的语境中,CString对象便可以自动转换成对应类型,从而方便使用者。

然而,我们熟知的另一个字符串类——标准库中的std::string却并没有提供自动转换成const char*的操作符重载,而是提供了一个函数c_str()来取代之。
为什么?
很显然这种方式让使用变得更加麻烦,而且让std::string的行为与标准字符串的差异更大。但以精巧著称的STL是显然不会不知道这点的,如果它不提供,肯定是有它的道理。

是的,这里面有trick:在特定情况下,随意的定义类型转换操作符可能导致未定义行为。

一个最简单的例子就足以说明这个问题:
我们知道std::string是有重载下标操作符(operator [])的,
假设std::string定义了一个char*类型转换操作符的话,那么遇到以下语句:
C++ 代码:
std:: string str =  "ABCDEFG";
str[2] =  'X';   // !!! ambiguous


这里的第二句就会出现问题。没看出来什么问题?好吧,解释下:
在这里,许多编译器会认为有歧义:
一方面,这个str[2]可以解释为对str应用了operator[]重载。
另一方面,这个str[2]又可以解释为在char*语境下做字符串索引,也就是说先对str应用char*类型转换重载,然后对转换后的char*应用语言本身的下标操作符。

而目前的C++标准并没有规定这种歧义如何化解,所以这是一个未定义的行为,有些编译器可能会按第一种来理解,有些编译器则可能按第二种来,有些编译器可能会报错,总之行为不确定。

因此重载了operator[]的std::string,安全起见就不再提供直接的类型转换操作符重载,而是使用一个语义明确的成员函数c_str()代之。


另一个操作符转换引起的古怪问题见于boost::shared_ptr(或scoped_ptr),这是一个智能指针类,和标准库的std::auto_ptr稍有类似,虽然实际特性有很多不同,但是我们这里只需要知道它是想智能的模拟C++语言中的指针的一个类。

它的一个功能就是,在bool语境中,能够自动转换成bool类型的能力。

说明确点,我们在用一个普通指针的时候,往往会这么用:
C++ 代码:
int* ptr;
// ......
if (ptr)  // 判断指针是否不为空
{
   // ...ooxx
}

这里的 if (ptr) 其实就是一个bool语境,指针这里会被自动转换成bool来处理:如果是NULL则为false,否则为true。

boost::share_ptr当然也可以这么做了:
C++ 代码:
boost::shared_ptr< int> ptr;
// ......
if (ptr)   // shared_ptr也能在这里自动转换成bool来判断是否为NULL
{
   // ...ooxx
}


这种特性提供了与语言原生指针相同的行为,用起来会很方便,但是它的实现,却并不像许多人想象的那样通过重载一个operator bool类型转换操作符来实现。
为什么?
假设shared_ptr中是采用重载operator bool的实现方式,那么在以下代码中,则会出现莫名其妙的问题:
C++ 代码:
shared_ptr< int> a( new  int(5));
shared_ptr< double> b( new  double(10.0));

if (a == b)   // ???!!!
{
   // ...
}

这里,我们定义了两个类型完全不同的智能指针,并且指向的是不同的位置,然后将他们进行比较。
这将两个不同类型的指针进行比较,在理论上说应该是导致编译错误的,然而事实上呢?这里不仅编译可以通过,而且比较的结果也是a == b!
对的,就是因为这里是一个bool语境,而如果重载了bool类型转换操作符的话,在这里a和b都会被自动转换成bool,从而能够进行比较。同时因为a和b都不是NULL,所以转换的结果都是true,于是a和b就莫名其妙的相等了。


boost是如何解决这个问题的呢?
它使用了一种称之为“orthodox”技术,如下:

C++ 代码:
template < class T>   class shared_ptr  

// stuff 
public

   struct PointerConversion  // 内部定义一个辅助类
  { 
     int valid; 
  }; 

operator  int PointerConversion::*()  const  //然后重载到这个辅助类的成员指针类型转换

   //如果是NULL则返回NULL,否则返回 shared_ptr<T>::PointerConversion::valid的相对地址 
   return rawptr_? &PointerConversion::valid : 0; 


private:  
  T * rawptr_;
};


它定义了一个辅助类,同时重载的并不是bool类型转换,而是这个辅助类的成员变量指针的类型转换。

这样,在bool语境中,该类仍然可以自动转换成一个成员指针类型,从而可以自动判断是否为NULL,同时,因为不同模板实例的这个成员指针的类型彼此不同,因此无法直接比较,会引起编译错误,从而避免了上述问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值