Modern C++ Design 笔记 第七章 Smart Pointers

还是不错的一章。尽管看了也用过了挺多的Smart Poniter了,但是看到这章还是引发了很多的遐想。
首先第一点,并非所有的只能指针都需要重载->和*这两个操作符,甚至要禁止这样的操作符,道理很简单。像操作系统中的这些Handle,这些东西本身就是访问系统资源的一个标记。并没有什么实际的成员函数,所以对他们而言,这样的* ->没有什么意义的。而如果要控制这些的访问级别,无疑反复提到的policy模型又有用武之地了(感觉这个Loki反复的强调了Policy的巨大意义)

第二点就是成员函数,这个topic是之前没有认真想过的,现在想来确实非常有意义。问题的简单描述就是这样,可能有人觉得给SmartPtr添加一些接口是不错的选择,比如说Acquire和Release用来管理实际指向的pointee_,但是问题在于如果看到一下的情形相信很多人会像我一样抓狂了:)

  1. SmartPtr<Printer> spRes = ...;
  2. spRes->Acquire(); //acuquire the printer
  3. ...// print a document...
  4. spRes->Release(); // release the printer
  5. spRes.Release();// release the pointer to the printer

spRes同时可以用.和->来调用名字相同的接口,无论如何都是让人很疑惑容易出错的东西,所以解决方案很简单,我们设计的智能指针不存在成员函数:)如果有这样效用的函数需求,可以把那些函数全局化,最多设成SmartPtr的友元。

第三点就是最重要,也是最主流的讨论点,就是OwerShip的问题。像auto_ptr那样的语义还是deep copy?(真没想到过他,好像主流的就是应用计数(Ref Count))。下面就这样的问题展开来看看到底这个OwnerShip有些什么主流的解决方案。
A. 最没有想到的是第一个Deep Copy,这个我们在C++中耳熟能详的技术在这里同样有着意义。当然第一反应就是和C++中的传统指针成员变量深拷贝的意义一致,就是每个SmartPtr都可以维护一个pointee_对象,那样就不存在什么拥有和删除的问题,每个人管自己的。但是这样最明显的问题就是这个语义和传统指针的意义已经不同了(这是我的观点啦)因为他们已经指向了两个内容相同的不同的对象(很拗口:))
比如说

  1. SmartPtr<Widget> spW1 = ....
  2. SmartPtr<Widget> spW2 = spW1;
  3. // this may change the content or status of instance spW2 points to.
  4. spW2->noneConstFunction()...
  5. // but now spW1 is still the same....

  6. //Compare with pointer
  7. Widget* pW1 = new Widget();
  8. Widget* pW2 = pW1;
  9. // this may change the content or status of instance spW2 points to.
  10. pW2->noneConstFunction()...
  11. // here pW1 changed as well...

恩,前面有点说废话了,回到这里的Deep Copy,那这个策略在这里的意义到底是什么呢?According to Andrei Alexandrescu,it's polymorphism,书中给出的理由是在拷贝的时候可以依赖C++那个虚函数Clone函数。这个Clone的函数保证了Slicing是不会发生的,所以原来的多态还是可以在新的只能指针对象中得以延续。但仔细想象就是鸡蛋问题,如果你不用深拷贝,根本就没有新的对象,根本不需要处理多态,但是现在反过来用多态作为深拷贝的一个理由,实在有点说不过去,这点是我最不置可否的。。。

B. Copy on Write也是一直很常见的技术,原来要表达的意思也是可以让多个指针指向同一个对象,等到某一个函数调用要真的影响到公共对象内容的时候才进行拷贝,是这个值变化不影响到其他的指针。但是在C++中关键的问题是不容易找到这样一个点来进行拷贝,是不是非Const的成员函数调用都需要copy呢?我们这里套用一句,copy或者不copy这是一个问题:)总而言之,你没有办法找到一个合适的插入点去决定是否有拷贝的发生,更极端的例子就是我用指针直接操纵public的成员变量的时候,确实我们什么都做不了,所以这个策略在这里并不适合。

C. Reference Counting
就像书里说到的一样,这个应该是最流行的方案了,满足了指针的语义,同时不需要额外的拷贝,唯一要做的就是多申请一个int值来存放。这个东西放在什么地方呢,这里有很多个解决方案
方案一



方案二:


方案三:(这个最为霸道:)侵入式的)


D. Reference Linking,
这个想法不知道是如何萌发的,但是其实还是一个不错的想法,说出来可能比较麻烦,但是画出一个图来就很清晰了

这里很明显每个pointee_指向的同一个对象,和引用技术不同的是他的指向同一个元素的策略是由一个双链表来支撑的。这里比之引用技术不好的地方就在于这个内存的使用上,很显然每个SmartPtr对象都需要一个prev和next指针的空间。更大的问题在于环形依赖,试想看这样的情况实例A有一个SmartPtr<B>,同时B里面有一个SmartPtr<A>。而SmartPtr当然是不能预见到侦测到这样的循环依赖的,会发生什么就不言而喻了。。。
E. Destructive Copy
这个就是std::auto_ptr的策略,就是每次拷贝的时候都需要把ownership搬来搬去,如果要代码的话就是这样:

  1. SmartPtr(SmartPtr& src)
  2. {
  3.      pointee_ = src.pointee_;
  4.      src.pointee_ = 0;
  5. }
  6. SmartPtr& operator=(SmartPtr& src)
  7. {
  8.      if (this != &src)
  9.      {
  10.          delete pointee_;
  11.          pointee_ = src.pointee_;
  12.          src.pointee_ = 0;
  13.       }
  14. }   

从上面的时候就可以看出,这是一种多么凶险的实现,如果显示的拷贝你是知道的那没有问题,如果是以值作为参数传递呢??是不是就传递了之后自身就失效了?!就是由于这种风险,auto_ptr并没有迎来足够多的掌声:)好了,到大致就是这么几种方案帮助了职能指针进行了对聚合指针的Ownership的维护.
最后这里我们还想讲的一个东西就是pointee_的地址的问题,或者是他本身的问题.
如果是地址的话,我们给出一个&()操作符来完成&pointee_(pointee_地址)的返回合适吗?回答是不合适!!!因为不知道什么时候作为参数传递的时候,有人删除了这个指针都未可知,而那个可怜的SmartPtr对此一无所知,后面的操作可以想象!!!
更可怕的事情是即使没有&操作符,一个*操作符足以出卖你得干干净净,由于有用户定义转换的存在,在编译以下代码的时候,编译器是不会报任何错误的

  1. SmartPtr<Widget> sp;
  2. delete sp; // compiler won't complain anything!!!

所以看来看去,指针指针毕竟不是指针,他所有的一切都是协调的产物,像指针又不是指针.真是一个挺尴尬的利器啊.好了今天就说到这里吧,^_^

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值