ITPub博客

首页 > Linux操作系统 > Linux操作系统 > More Effective C++ 条款28(下) (转)

More Effective C++ 条款28(下) (转)

原创 Linux操作系统 作者:WebSnap 时间:2019-05-05 14:36:05 0 删除 编辑
More Effective C++ 条款28(下) (转)

条款28:灵巧(smart)指针(下)

  译者注:由于我无法在文档区贴上图片(在论坛询问,结果无人回答),所以只能附上此译文的word文档。.NET/FileBBS/files/2001_12/T_967_1.zip">下载

这种技术能给我们几乎所有想要的行为特性。假设我们用一个新类CasSingle来扩充MusicProduct类层次,用来表示cassette singles。修改后的类层次看起来象这样:XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />

ASPectratio="t" v:ext="edit">

现在考虑这段代码:

template  // 同上, 包括作为类型


class SmartPtr { ... };  // 转换操作符的成员模板


 


void displayAndPlay(const SmartPtr& pmp,


   int howMany);


 


void displayAndPlay(const SmartPtr& pc,


  int howMany);


 


SmartPtr dumbMusic(new CasSingle("Achy Breaky Heart"));


 


displayAndPlay(dumbMusic, 1);  // 错误!


在这个例子里,displayAndPlay被重载,一个函数带有SmartPtr 对象参数,其它函数的参数为SmartPtr,我们期望调用SmartPtr,因为CasSingle是直接从Cassette上继承下来的,而它仅仅是间接继承自MusicProduct。当然这是dumb指针的工作方法,我们的灵巧指针不会这么灵巧。它们把成员函数做为转换操作符来使用,就C++编译器而言,所有类型转换操作符都一样,没有好坏的分别。因此displayAndPlay的调用具有二义性,因为从SmartPtr 到SmartPtr的类型转换并不比到SmartPtr的类型转换好。

通过成员模板来实现灵巧指针的类型转换有还有两个缺点。第一,支持成员模板的编译器较少,所以这种技术不具有可移植性。以后情况会有所改观,但是没有人知道这会等到什么时候。第二,这种方法的工作原理不很明了,要理解它必须先要深入理解函数调用的参数匹配,隐式类型转换函数,模板函数隐式实例化和成员函数模板。有些程序员以前从来没有看到过这种技巧,而却被要求维护使用这种技巧的代码,我真是很可怜他们。这种技巧确实很巧妙,这自然是肯定,但是过于的巧妙可是一件危险的事情。

不要再拐弯抹角了,直接了当地说,我们想要知道的是在继承类向基类进行类型转换方面,我们如何能够让灵巧指针的行为与dumb指针一样呢?答案很简单:不可能。正如Daniel Edelson所说,灵巧指针固然灵巧,但不是指针。最好的方法是使用成员模板生成类型转换函数,在会产生二义性结果的地方使用casts(参见条款2)。这不是一个完美的方法,不过也很不错,在一些情况下去除二义性,所付出的代价与灵巧指针提供复杂的功能相比还是值得的。

灵巧指针和const

对于dumb指针来说,const既可以针对指针所指向的东西,也可以针对于指针本身,或者兼有两者的含义(参见Effective C++条款21):

CD goodCD("Flood");


 


const CD *p;  // p 是一个non-const 指针


  //指向 const CD 对象


 


CD * const p = &goodCD;  // p 是一个const 指针


 // 指向non-const CD 对象;


  // 因为 p 是const, 它


  // 必须被初始化


 


const CD * const p = &goodCD;  // p 是一个const 指针


  // 指向一个 const CD 对象


我们自然想要让灵巧指针具有同样的灵活性。不幸的是只能在一个地方放置const,并只能对指针本身起作用,而不能针对于所指对象:

const SmartPtr p =  // p 是一个const 灵巧指针


  &goodCD;  // 指向 non-const CD 对象

好像有一个简单的补救方法,就是建立一个指向cosnt CD的灵巧指针:

SmartPtr p =  // p 是一个 non-const 灵巧指针


  &goodCD;  // 指向const CD 对象


现在我们可以建立const和non-const对象和指针的四种不同组合:


SmartPtr p;  // non-const 对象


  // non-const 指针


 


SmartPtr p;  // const 对象,


  // non-const 指针


 


const SmartPtr p = &goodCD;  // non-const 对象


  // const指针


 


const SmartPtr p = &goodCD;  // const 对象


  // const 指针


 


但是美中不足的是,使用dumb指针我们能够用non-const指针初始化const指针,我们也能用指向non-cosnt对象的指针初始化指向const对象的指针;就像进行赋值一样。例如:

CD *pCD = new CD("Famous Movie Themes");


 


const CD * pConstCD = pCD;  // 正确


但是如果我们试图把这种方法用在灵巧指针上,情况会怎么样呢?

SmartPtr pCD = new CD("Famous Movie Themes");


 


SmartPtr pConstCD = pCD;  // 正确么?


SmartPtr 与SmartPtr是完全不同的类型。在编译器看来,它们是毫不相关的,所以没有理由相信它们是赋值兼容的。到目前为止这是一个老问题了,把它们变成赋值兼容的惟一方法是你必须提供函数,用来把SmartPtr类型的对象转换成SmartPtr类型。如果你使用的编译器支持成员模板,就可以利用前面所说的技巧自动生成你需要的隐式类型转换操作。(我前面说过,只要对应的dumb指针能进行类型转换,灵巧指针也就能进行类型转换,我没有欺骗你们。包含const类型转换也没有问题。)如果你没有这样的编译器,你必须克服更大的困难。

包括const的类型转换是单向的:从non-const到const的转换是安全的,但是从const到non-const则不是安全的。而且用const指针能的事情,用non-const指针也能做,但是用non-const指针还能做其它一些事情(例如,赋值操作)。同样,用指向const的指针能做的任何事情,用指向non-const的指针也能做到,但是用指向non-const的指针能够完成一些使用指向const的指针所不能完成的事情(例如,赋值操作)。

这些规则看起来与public继承的规则相类似(Effective C++ 条款35)。你能够把一个派生类对象转换成基类对象,但是反之则不是这样,你对基类所做的任何事情对派生类也能做,但是还能对派生类做另外一些事情。我们能够利用这一点来实作灵巧指针,就是说可以让每个指向T的灵巧指针类public派生自一个对应的指向const-T的灵巧指针类:

template  // 指向const对象的


class SmartPtrToConst {  // 灵巧指针


 


  ...   // 灵巧指针通常的


  // 成员函数


 


protected:


  union {


  const T* constPointee;  // 让 SmartPtrToConst 访问


  T* pointee;  // 让 SmartPtr 访问


  };


};


 


template  // 指向non-const对象


class SmartPtr:  // 的灵巧指针


  public SmartPtrToConst {


  ...  // 没有数据成员


};


使用这种设计方法,指向non-const-T对象的灵巧指针包含一个指向const-T的dumb指针,指向const-T的灵巧指针需要包含一个指向cosnt-T的dumb指针。最方便的方法是把指向const-T的dumb指针放在基类里,把指向non-const-T的dumb指针放在派生类里,然而这样做有些浪费,因为SmartPtr对象包含两个dumb指针:一个是从SmartPtrToConst继承来的,一个是SmartPtr自己的。

一种在C世界里的老式武器可以解决这个问题,这就是union,它在C++中同样有用。Union在protected中,所以两个类都可以访问它,它包含两个必须的dumb指针类型,SmartPtrToConst对象使用constPointee指针,SmartPtr对象使用pointee指针。因此我们可以在不分配额外空间的情况下,使用两个不同的指针(参见Effective C++条款10中另外一个例子)这就是union美丽的地方。当然两个类的成员函数必须约束它们自己仅仅使用适合的指针。这是使用union所冒的风险。

利用这种新设计,我们能够获得所要的行为特性:

SmartPtr pCD = new CD("Famous Movie Themes");


 


SmartPtrToConst pConstCD = pCD;  // 正确


 


评价

有关灵巧指针的讨论该结束了,在我们离开这个话题之前,应该问这样一个问题:灵巧指针如此繁琐麻烦,是否值得使用,特别是如果你的编译器缺乏支持成员函数模板时。

经常是值得的。例如通过使用灵巧指针极大地简化了条款29中的引用计数代码。而且正如该例子所显示的,灵巧指针的使用在一些领域受到极大的限制,例如测试空值、转换到dumb指针、继承类向基类转换和对指向const的指针的支持。同时灵巧指针的实作、理解和维护需要大量的技巧。Debug使用灵巧指针的代码也比Debug使用dumb指针的代码困难。无论如何你也不可能设计出一种通用目的的灵巧指针,能够替代dumb指针。

达到同样的代码效果,使用灵巧指针更方便。灵巧指针应该谨慎使用, 不过每个C++程序员最终都会发现它们是有用的。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10797429/viewspace-101621/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-01-04

  • 博文量
    163
  • 访问量
    121781