More Effective C++ 条款28(下)

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

      译者注:由于我无法在文档区贴上图片(在论坛询问,结果无人回答),所以只能附上此译文的word文档。下载

这种技术能给我们几乎所有想要的行为特性。假设我们用一个新类CasSingle来扩充MusicProduct类层次,用来表示cassette singles。修改后的类层次看起来象这样:

现在考虑这段代码:

template<class T>                    // 同上, 包括作为类型
   
class SmartPtr { ... };              // 转换操作符的成员模板
   
 
  
void displayAndPlay(const SmartPtr<MusicProduct>& pmp,
  
                    int howMany);
  
 
  
void displayAndPlay(const SmartPtr<Cassette>& pc,
  
                    int howMany);
  
 
  
SmartPtr<CasSingle> dumbMusic(new CasSingle("Achy Breaky Heart"));
  
 
  
displayAndPlay(dumbMusic, 1);        // 错误!
   

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

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

不要再拐弯抹角了,直接了当地说,我们想要知道的是在继承类向基类进行类型转换方面,我们如何能够让灵巧指针的行为与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<CD> p =                // p 是一个const 灵巧指针
   

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

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

SmartPtr<const CD> p =            // p 是一个 non-const 灵巧指针
   
  &goodCD;                        // 指向const CD 对象
   
现在我们可以建立constnon-const对象和指针的四种不同组合:
   
SmartPtr<CD> p;                          // non-const 对象
   
                                         // non-const 指针
   
 
  
SmartPtr<const CD> p;                    // const 对象,
   
                                         // non-const 指针
   
 
  
const SmartPtr<CD> p = &goodCD;          // non-const 对象
   
                                         // const指针
   
 
  
const SmartPtr<const CD> p = &goodCD;    // const 对象
   
                                         // const 指针
   
 
  

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

CD *pCD = new CD("Famous Movie Themes");
  
 
  
const CD * pConstCD = pCD;               // 正确
   

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

SmartPtr<CD> pCD = new CD("Famous Movie Themes");
  
 
  
SmartPtr<const CD> pConstCD = pCD;       // 正确么?
   

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

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

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

template<class T>                    // 指向const对象的
   
class SmartPtrToConst {              // 灵巧指针
   
 
  
  ...                                // 灵巧指针通常的
   
                                     // 成员函数
   
 
  
protected:
  
  union {
  
    const T* constPointee;           //  SmartPtrToConst 访问
   
    T* pointee;                      //  SmartPtr 访问
   
  };
  
};
  
 
  
template<class T>                    // 指向non-const对象
   
class SmartPtr:                      // 的灵巧指针
   
  public SmartPtrToConst<T> {
  
  ...                                // 没有数据成员
   
};
  

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

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

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

SmartPtr<CD> pCD = new CD("Famous Movie Themes");
  
 
  
SmartPtrToConst<CD> pConstCD = pCD;     // 正确
   
 
  

评价

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

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

达到同样的代码效果,使用灵巧指针更方便。灵巧指针应该谨慎使用 , 不过每个 C++ 程序员最终都会发现它们是有用的。
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
引介:一本绝妙好书 /孟岩 您手上这本书,是世界顶级C++大师Scott Meyers成名之作的第二版。其第一版诞生于1991年。在国际上,本书所引起的反响之大,波及整个计算机技术出版领域,余音至今未绝。几乎在所有C++书籍的推荐名单上,本书都会位于前三名。作者高超的技术把握力,独特的视角、诙谐轻松的写作风格、独具匠心的内容组织,都受到极大的推崇和仿效。甚至连本书简洁明快的命名风格,也有着一种特殊的号召力,我可以轻易列举出一大堆类似名字,比如Meyers本人的More Effective C++Effective STL,Don Box的Effective COM,Stan Lippman主编的Efficient C++系列,Herb Sutter的Exceptional C++等等。要知道,这可不是出版社的有意安排,而且上面这些作者,同样是各自领域里的绝顶大师,决非人云亦云、欺世盗名之辈。这种奇特的现象,只能解释为人们对这本书衷心的赞美和推崇。 然而这样一本掷地有声的C++世界名著,不仅迟迟未能出版简体中文版,而且在国内其声誉似乎也并不显赫。可以说在一年之前,甚至很少有C++的学习者听说过这本书,这实在是一种遗憾。今天,在很多人的辛勤努力之,这本书终于能够展现在我们的面前,对于真正的C++程序员来说,这确实是一件值得弹冠相庆的事。 我是一名普通的C++爱好者,因为机缘巧合,有幸参与了这本书的繁简转译工作,这使我能够比较早地看到本书的原版和繁体中文版。在这里我必须表达对本书中文译者、台湾著名技术作家侯捷先生的敬意和感谢,因为在我看来,这本书的中文版在质量上较其英文版兄长分毫不差,任何人都知道,达到这一点是多么的困难。侯先生以其深厚的技术功底、卓越的语言能力和严谨细致的治学态度,为我们跨越了语言隔阂所带来的理解障碍,完整而生动地将原书的内容与精神表达无遗,更令人钦佩的是,中文版的行文风格与原文也达到了高度的统一,可谓神形兼备,实在令人赞叹!因此我非常乐意向大家推荐这本书,相信它会在带给您带给你技术享受的同时,也带给您阅读的享受。 曾经在网络讨论组中间看到这样的说法,C++程序员可以分成两类,读过Effective C++的和没读过的。或许有点夸张了,但无论如何,当您拥有这本书之后,就获得了迅速提升自己C++功力的一个契机。这本书不是读完一遍就可以束之高阁的快餐读物,也不是能够立刻解决手边问题的参考手册,而是需要您去反复阅读体会,极力融入自己思想之中,融入自己每一次敲击键盘的动作之中。C++是真正程序员的语言,背后有着精深的思想与无以伦比的表达能力,这使得它具有类似宗教般的魅力。希望这本书能够帮助您跨越C++的重重险阻,领略高处才有的壮美,做一个成功而快乐的C++程序员。 <p><b><font color="#FF0000">侯捷又一力作</font></b> 继 Effective C++ 之後,Scott Meyers 於 1996 推出这本「续集」。条款变得比较少,页数倒是多了一些,原因是这次选材比「第一集」更高阶,尤其是第五章。Meyers 将此章命名为技术Techniques,并明白告诉你,其中都是一些 patterns,例如 virtual ctors、smart pointers、reference counting、proxy classes,double dispatching┅等等。这一章的每个条款篇幅都在 15~30 页之谱,实在让人有「山穷水尽疑无路,柳暗花明又一村」之叹。 虽然出版年代稍嫌久远,本书并没有第二版,原因是当其出版之时1996C++ Standard 已经几乎定案,本书即依当时的标准草案而写。其间与现今之 C++ 标准规格几乎相同。可能变化的几个弹性之处,Meyers 也都有所说明与提示。读者可以连结作者提供的网址,看看上两集的勘误与讨论数量之多,令人惊恐。幸好多是技术讨论或文字斟酌,并没有什麽重大误失。 <font color="#FF6600"><b>本书样章试读:</b></font><a href="../../../temporary/list/cooperate/zipdownload/9587/01.zip"><font color="#FF6600">第1章</font></a><a href="http://www.china-pub.com/computers/common/info.asp?id=9588" target="_blank"> 《Exceptional C++中文版》</a><font color="#FF0000">(已经出版)</font> <a href="http://www.china-pub.com/computers/bookinfo/dianlihj.htm#1" target="_blank">深入C++系列其它图书...</a></p>
<p> <span style="font-size:14px;color:#337FE5;">【为什么学爬虫?】</span> </p> <p> <span style="font-size:14px;">       1、爬虫入手容易,但是深入较难,如何写出高效率的爬虫,如何写出灵活性高可扩展的爬虫都是一项技术活。另外在爬虫过程中,经常容易遇到被反爬虫,比如字体反爬、IP识别、验证码等,如何层层攻克难点拿到想要的数据,这门课程,你都能学到!</span> </p> <p> <span style="font-size:14px;">       2、如果是作为一个其他行业的开发者,比如app开发,web开发,学习爬虫能让你加强对技术的认知,能够开发出更加安全的软件和网站</span> </p> <p> <br /> </p> <span style="font-size:14px;color:#337FE5;">【课程设计】</span> <p class="ql-long-10663260"> <span> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 一个完整的爬虫程序,无论大小,总体来说可以分成三个步骤,分别是: </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 网络请求:模拟浏览器的行为从网上抓取数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据解析:将请求来的数据进行过滤,提取我们想要的数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据存储:将提取到的数据存储到硬盘或者内存中。比如用mysql数据库或者redis等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 那么本课程也是按照这几个步骤循序渐进的进行讲解,带领学生完整的掌握每个步骤的技术。另外,因为爬虫的多样性,在爬取的过程中可能会发生被反爬、效率低等。因此我们又增加了两个章节用来提高爬虫程序的灵活性,分别是: </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 爬虫进阶:包括IP代理,多线程爬虫,图形验证码识别、JS加密解密、动态网页爬虫、字体反爬识别等。 </li> <li class="" style="font-size:11pt;color:#494949;"> Scrapy和分布式爬虫:Scrapy框架、Scrapy-redis组件、分布式爬虫等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 通过爬虫进阶的知识点我们能应付大量的反爬网站,而Scrapy框架作为一个专业的爬虫框架,使用他可以快速提高我们编写爬虫程序的效率和速度。另外如果一台机器不能满足你的需求,我们可以用分布式爬虫让多台机器帮助你快速爬取数据。 </p> <p style="font-size:11pt;color:#494949;">   </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 从基础爬虫到商业化应用爬虫,本套课程满足您的所有需求! </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;background-color:#FFFFFF;color:#337FE5;">【课程服务】</span> </p> <p> <span style="font-size:14px;">专属付费社群+定期答疑</span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"><br /> </span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"></span> </p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值