智能指针有什么不足之处?

论坛 期权论坛 知乎     
知乎de用户   2019-8-18 00:57   17727   5
转载声明:本文由互联网用户自发贡献,部分转载来源来自知乎(zhihu.com),强烈建议您访问知乎查看完整内容。本社区不拥有所有权,也不承担任何法律责任。如有侵权,请联系optbbs@163.com。一经查实,即刻删除。
我能想到的
1 循环引用
2 基于引用计数的一些性能损耗

还有其他的缺点吗?相对比来说GC比智能指针又有什么优势呢?
分享到 :
0 人收藏

5 个回复

倒序浏览
2#
热心回应  16级独孤 | 2019-8-18 00:57:43 发帖IP地址来自
我觉得herb sutter已经把c++指针这个问题讲完了,什么时候raw,什么时候unique ,什么时候shared,参考他的演讲吧,貌似叫leak freedom by default

当然还有什么value ptr,和内存管理关系不是很大
3#
热心回应  16级独孤 | 2019-8-18 00:57:44 发帖IP地址来自
智能指针的设计理念是没有问题的,各位说风格不一致问题的,是因为还在用老的理念用新东西。
和普通指针相比,智能指针通过引用计数,实现了资源回收。智能指针是为了减轻程序员直接管理内存。和new混杂使用是习惯问题,不是智能指针的问题。
围绕引用计数,带来轻微的性能损失,如多线程下,引用计数的管理。另一个是,类型转换问题。
大家需要的是,与时俱进,而不是用老眼光看待新东西。
4#
热心回应  16级独孤 | 2019-8-18 00:57:45 发帖IP地址来自
1.  出现的太晚了
2.  第一印象被auto_ptr搞臭了
3.  让某些问题浮出水面而不是暗藏水底,从而招致反感。最典型的问题就是对象的所有权,在裸指针new加delete时代,对象的所有权是暗含在谁new谁delete的逻辑上的,哪怕代码被写的所有权关系暗流涌动都随便,反正最后能delete就行,真有了暗圈也不过是捞住一个入口delete一下罢了。到了智能指针时代所有权问题浮出水面,有人看到乱象了、看到有圈了,瞬间感觉不能忍,“这一定是智能指针的问题!”
5#
热心回应  16级独孤 | 2019-8-18 00:57:46 发帖IP地址来自
先做个孔乙己,题主问的应该是使用引用计数的shared_ptr这种吧,其实广义上说只要是把指针用一个指针like的类封装起来,然后利用一些语法特性(构造,析构等)进行管理都算smart,引用计数算是最直观的,但也有其他做法的哦
具体到shared_ptr的不足,其实重要的是看你想把它用到什么程度,比如说有答主认为weak_ptr可以解决(虽然个人认为只是一种缓解)循环引用,当然这会给代码设计和开发带来一些不便,但只要你能接受,就可以不认为它是一种“不足”,所以我这里说的几点不足也是有程度之分
首先是性能问题,这个有比较大的争议,个人看法是不太可能成为最大瓶颈,但也不会到了可以忽视的地步,和你具体的代码相关
然后是循环引用,这个在引用计数范畴是没法彻底解决的,必须引入其他一些算法或设计,或程序员自己保证不要出现循环引用,这块众所周知了也不细说
在C++中可能存在一个裸传this导致的问题,比如这个代码(不要在意可能的细节错误,当伪代码看就好):
  1. shared_ptr p;class T {int member;void m() {    //仍然只有p引用这个对象,但是实际上this也引用了    p = NULL; //导致main中new的T对象被销毁    this->member; //访问了非法的地址}}int main() {    p = new T; //给p初始化T的对象,这时候只有p引用这个对象    p->m();}
复制代码
其实就是当你通过shared_ptr去调用方法的时候,由于C++将this作为一个隐式的方法参数,用裸指针传入,而原则上这时候应该对其引用计数+1,但是没加,假设在方法中直接或间接地使得this指向对象被销毁,那就挂了
解决这个问题需要在调用方法时增加this的引用,简单点就改成:
shared_ptr(p)->m();
用临时对象来保管一个引用,C++规定临时对象在full expression结束才销毁,所以至少能保证在方法执行完成是安全的
注意这里用全局变量做例子,其实还有其他一些情况,比如p存在堆上,而m中或m调用的其他函数中可能对其做修改,都有这个风险
但是呢,如果每次调用方法都copy出一个临时shared_ptr,太麻烦了,再说这个情况可能也非常少见,所以这事挺矛盾的,做了性价比不高,不做的话又总有一个理论上的崩溃可能留在那
下一个问题并不是智能指针本身的问题,但我觉得也有一些关系,假设我们用C++写个单链表:
  1. class Node {Node *next;~Node() {    if (next != NULL) {        delete next;    }}}
复制代码
于是我们只需要delete head,就可以利用析构链式释放整个表,但是显然这是一个递归,当链表很长的时候,递归过深就有问题了
虽然看上去和shared_ptr没关系,但我个人觉得,用引用计数管理对象有时候很容易出这种事情,比如这个python代码:
  1. a = Nonefor i in xrange(N):    a = (a,)a = None
复制代码
其实就是上面case的py版本,用tuple做node,构造长度为N的链表,然后直接释放,CPython用的引用计数,所以N足够大且运行栈不大(比如win下好像只有1M)的时候这里会崩掉
怎么说呢,这不是智能指针和引用计数的问题,但是用引用计数的场景下,这个问题可能又容易被人忽视,当然有经验的程序员会避开这个坑了
派生类的智能指针向基类转换的问题有答主说了,貌似有的shared_ptr模板库提供了解决办法,不过我没去看怎么解决的
应该还有一些其他问题,暂时还没想到或想起来,我最不爽的是写起来太麻烦了,如果能像java之类的语法写代码,然后底层自动用shared_ptr不是很好么,所以我做了这个项目(但shared_ptr的麻烦并不是其主要动机):
maopao-691515082/coc-lang这个语言通过编译到C++来执行,其中对象管理用的就是基于引用计数的智能指针,针对上面的一些问题,在编译器上做了优化解决,比如在函数嵌套调用的时候,如果上层函数栈已经维持了某对象的引用,则这个对象被当做是参数传入下层被调用函数时,尽量使用裸指针(因为栈是后入先出,所以这个对象不可能被提前释放),再比如上面说的裸传this问题,也会在编译期做识别,只在必要的时候做shared_ptr的copy
====================
补充,想起来还有个重要的地方是,引用计数在多线程环境下不好弄,因为引用计数的更新不是原子操作,需要加锁,哪怕不考虑锁的数量问题(每个对象一个互斥锁是不可取的),频繁加解锁性能也接受不了,当然,也有一些轻量级的办法,比如cas或利用其它办法实现的无锁,但在我看来这也算是一种广义的锁互斥,毕竟面对的问题是一个互斥问题嘛
也正是因为引用计数的这个缺点,CPython使用了“臭名昭著”的GIL,喷的人很多,但很少有人把它和GC联系起来
6#
热心回应  16级独孤 | 2019-8-18 00:57:47 发帖IP地址来自
我来说个实际点的,Covariant Return Type:
  1. class B {public:    virtual B* clone() const {        return new B;    }};class D : public B {public:    D* clone() const override {        return new D;    }};
复制代码
对于 smart pointer 是不支持的:
  1. class B {public:    virtual std::shared_ptr clone() const {        return std::make_shared();    }};class D : public B {public:    std::shared_ptr clone() const override {        return std::make_shared();    }};
复制代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:55334
帖子:27423
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP