weak_ptr这个智能指针有什么用?

转自: https://segmentfault.com/q/1010000004078858

c++primer:

通过使用weak_ptr,不会影响一个给定的strblob所指向的vector的生存期。但是,可以阻止用户访问一个不再存在的vector的企图。

书上说的作用就这一个,我想不通的是,阻止用户访问一个不再存在的vector,难道就不能通过检测strblob的shared_ptr是否为NULL来实现吗?

补充1:为什么不能检测shared_ptr是否为NULL?

因为这就是错的。shared_ptrnullptr相等,并不代表所引用的对象不存在了。

shared_ptr允许有一个“empty”状态,代表shared_ptr<T>对象的实例本身存在,但并不指向一个有效的Tshared_ptr重载了===来表示以下意义:

  • shared_ptrnullptr判等,代表判断是否处在empty状态;

  • shared_ptr被赋值为nullptr,不代表shared_ptr实例本身没了,而是把这个shared_ptr实例的状态改为empty。

  • 一个已经处于empty状态的shared_ptr仍可以被继续赋值成为一个有效、有值的shared_ptr

如果有两个shared_ptr同时指向同一个T在内存中的实例,那么任意一个shared_ptrnullptr判等成功,都不能说明指向的对象已经不存在(被销毁)了!

任何一个具体用例中,都必然有多个shared_ptr指向同一个实例(如果没有多个你shared干嘛)。所以通过判空某 1 个shared_ptr去确定被指向的对象是否已被销毁,这个在语义和实效上都绝无任何正确(哪怕是“蒙对了”)的可能!


补充2:shared_ptr到底是什么?

首先shared_ptr虽然名为“智能指针”,但其实他不是指针。一个shared_ptr<T>定义出来的东西,只是一个单纯的变量,存储了一个实例化出来的class。其本质上等同于以下代码:

struct MyClass
{
public:
    int MyValue;
    MyClass(int my_value) {
        MyValue = my_value;
    }
}

void main()
{
    auto a = new MyClass(1); 
    // a 是一个单纯的变量,不是指针
    // a 天然与 main() 函数拥有等同的生命周期
}

为什么shared_ptr能够像指针一样使用*&->等运算符,是因为shared_ptr类把这些运算符重载了,从而让程序编写者“看起来”像是在用指针而已。

一个非指针的局部变量,如果不靠运算符重载,你是不能判nullptr的。如同你不能对一个intnullptr一样。

shared_ptrnullptr由于运算符重载,其本质意义都变了,必须具体分析,而不能想当然!


嗯……非读者注意:StrBlob仅仅是那本书里的一个范例类,其内部用shared_ptr维护其管理的vector<string>集合本身的生存期。与C++本身无关。

阻止用户访问一个不再存在的vector,这个用shared_ptr没问题,能做到。事实上很多情况下,weak_ptr全改shared_ptr并不会立刻就炸。

但是shared_ptr就意味着你的引用和原对象是一个强联系。你的引用不解开,原对象就不能销毁。滥用强联系,这在一个运行时间长、规模比较大,或者是资源较为紧缺的系统中,极易造成隐性的内存泄漏,这会成为一个灾难性的问题。

更糟的是,滥用强联系可能造成循环引用的灾难。即:B持有指向A内成员的一个shared_ptrA也持有指向B内成员的一个shared_ptr,此时AB的生命周期互相由对方决定,事实上都无法从内存中销毁。 
——这还仅仅是一个简单的情况。如果存在间接的强引用,或者是多于两个实例之间的强引用,这个相关的bug解起来将是灾难性的。

shared_ptr是C++内存管理机制的一种放松。但放松绝不意味着可以滥用,否则最后的结局恐怕不会比裸指针到处申请了不释放更好。

必须明确:从语义上来说,shared_ptr代表了一种对生命周期的自动推断。其本质的意义是:A持有Bshared_ptr,代表B的生命周期反而完全覆盖了A。以树形结构的层级来理解,指针持有者是下级,指针指向的目标反而才是上级——下级短命,上级长存;上级不存,下级焉附。

从这个意义上,有些关系你用shared_ptr就表示不了了:

  • 隶属关系中,祖先到子孙的关系。比如一个对象容器,具体对象找其隶属的容器可以用shared_ptr,但是容器去找具体的对象则不行,因为容器不能要求对象生存的比自己更久。

  • 生命周期没有本质关联的两个无关对象。例如一个全局事件管理器(订阅者模型),事件订阅者构造时把自己注册进来,析构时把自己解注册掉。管理器要维护到达这个对象的指针(从而发送消息),但绝对不允许染指对象的生命周期,对象的析构需要无视订阅者的存在,只由其他业务所必须的强引用来控制。

这种时候就是weak_ptr的用处。weak_ptr提供一个(1)能够确定对方生存与否(2)互相之间生命周期无干扰(3)可以临时借用一个强引用(在你需要引用对方的短时间内保证对方存活)的智能指针。

weak_ptr要求程序员在运行时确定生存并加锁,这也是逻辑上必须的本征复杂度——如果别人活的比你短,你当然要:(1)先确定别人的死活(2)如果还活着,就给他续个命续到你用完了为止。


事实上弱引用强引用这个概念,在有GC的语言中也是一个需要注意的问题。以C#为例:

public class EventDisposer
{
    private Dictionary<int, WeakReference<IEventListener>> pendingEvents;
    private void Dispose(int event_id)
    {
        var reference = pendingEvents[event_id];
        IEventListener context;
        if (reference.TryGetTarget(out context)) 
        {
            // context is valid from here to the end of function
            context.Trigger();
        }
        else
        {
            // context has already been destroyed...
        }
    }
}

这个程序使用WeakReference<IEventListener>替代强引用的IEventListener,从而使接受事件分发的对象,在生存周期上可以获得彻底销毁的自由,而不和事件分发者产生什么必然的关联。

  •  评论 · 12
  • 赞赏
  • 编辑

沙渺  21.3k

2015-12-01 更新

其实weak_ptr一般就是用来防止循环引用的。shared_ptr就是其他语言常见的强引用

— DreammingPiggy · 2015年12月01日

回复 DreammingPiggy

我的理解,循环引用只是滥用shared_ptr的某一种结果。我们并不是因为”weak_ptr防止循环引用“而使用它,而是因为”我们未必需要shared_ptr去实现生命周期的强包含“这条本质理由而使用它。——我相信除了循环引用之外,罔顾这条判断标准去滥用shared_ptr还会有更多的不良后果发生。

— 沙渺 · 2015年12月01日

你的回答挺完整的,不过对我的问题并没有正面回答。我在csdn上同样的问题得到了非常简短的回答:不能,因为它不存在了

— 那儿有个活人 · 2015年12月01日

回复 那儿有个活人

这个答案结论正确,但理由大错特错!因为shared_ptr究其本身,其实是一个泛型类实例化之后的对象实例,本质上等同于我们不带星的去定义一个struct——它自己连指针都不是,又怎么可能等于NULL?!更多具体的内容,我在答案内补充。

— 沙渺 · 2015年12月01日

回复 沙渺

是我提问不对,我后来才注意到shared_ptr是类

 

— 那儿有个活人 · 2015年12月01日

回复 那儿有个活人

sorry,有个再次更新——shared_ptr其实是可以和nullptr判空的,请参见答案的再次补充。

— 沙渺 · 2015年12月01日

回复 那儿有个活人

补充完毕。我的最终结论是:csdn的仁兄完全答错了,不光是理由错了,结论也错。欢迎你阅读最新的答案。

— 沙渺 · 2015年12月01日

回复 沙渺

仁兄的认真程度让我震撼,格式也十分美观

 

— 那儿有个活人 · 2015年12月01日

回复 沙渺

仁兄,我感觉你没理解csdn那个人的意思。他的意思是如果指向的对象真的不存在了,那么对应的shared_ptr对象也都析构掉了,既然对象都没了,其他的操作也就无从谈起了

— 那儿有个活人 · 2015年12月01日

我先睡觉了

 

— 那儿有个活人 · 2015年12月01日

回复 那儿有个活人

他漏了shared_ptr被置空的可能性。被置空的shared_ptr你不能说他不存在,编译器更允许你去判nullptr,但你就是不能这样去判断“置空之前这个shared_ptr所指的对象,现在到底存活与否”——因为语义错了。

— 沙渺 · 2015年12月02日

回复 沙渺

shared_ptr被置空似乎要用户主动调用reset()成员函数才行。但书上这句话说vector不再存在的意思是,当指向vector的最后一个shared_ptr被销毁时,它所指的vector对象也随之自动销毁的情况。既然shared_ptr本身都不存在了,也就没有置不置空的这种可能性。

— 那儿有个活人 · 2015年12月02日

 

提交评论

×评论支持部分 Markdown 语法:**bold**_italic_[link](http://example.com)> 引用`code`- 列表
同时,被你 @ 的用户也会收到通知

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值