Heim > Fragen und Antworten > Hauptteil
c++primer:
通过使用weak_ptr,不会影响一个给定的strblob所指向的vector的生存期。但是,可以阻止用户访问一个不再存在的vector的企图。
书上说的作用就这一个,我想不通的是,阻止用户访问一个不再存在的vector,难道就不能通过检测strblob的shared_ptr是否为NULL来实现吗?
PHP中文网2017-04-17 13:07:59
补充1:为什么不能检测shared_ptr
是否为NULL?
因为这就是错的。shared_ptr
和nullptr
相等,并不代表所引用的对象不存在了。
shared_ptr
允许有一个“empty”状态,代表shared_ptr<T>
对象的实例本身存在,但并不指向一个有效的T
。shared_ptr
重载了==
和=
来表示以下意义:
shared_ptr
和nullptr
判等,代表判断是否处在empty状态;
shared_ptr
被赋值为nullptr
,不代表shared_ptr
实例本身没了,而是把这个shared_ptr
实例的状态改为empty。
一个已经处于empty状态的shared_ptr
仍可以被继续赋值成为一个有效、有值的shared_ptr
。
如果有两个shared_ptr
同时指向同一个T
在内存中的实例,那么任意一个shared_ptr
和nullptr
判等成功,都不能说明指向的对象已经不存在(被销毁)了!
任何一个具体用例中,都必然有多个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
的。如同你不能对一个int
判nullptr
一样。
shared_ptr
判nullptr
由于运算符重载,其本质意义都变了,必须具体分析,而不能想当然!
嗯……非读者注意:StrBlob
仅仅是那本书里的一个范例类,其内部用shared_ptr
维护其管理的vector<string>
集合本身的生存期。与C++本身无关。
阻止用户访问一个不再存在的vector
,这个用shared_ptr
没问题,能做到。事实上很多情况下,weak_ptr
全改shared_ptr
并不会立刻就炸。
但是shared_ptr
就意味着你的引用和原对象是一个强联系。你的引用不解开,原对象就不能销毁。滥用强联系,这在一个运行时间长、规模比较大,或者是资源较为紧缺的系统中,极易造成隐性的内存泄漏,这会成为一个灾难性的问题。
更糟的是,滥用强联系可能造成循环引用的灾难。即:B
持有指向A
内成员的一个shared_ptr
,A
也持有指向B
内成员的一个shared_ptr
,此时A
和B
的生命周期互相由对方决定,事实上都无法从内存中销毁。
——这还仅仅是一个简单的情况。如果存在间接的强引用,或者是多于两个实例之间的强引用,这个相关的bug解起来将是灾难性的。
shared_ptr
是C++内存管理机制的一种放松。但放松绝不意味着可以滥用,否则最后的结局恐怕不会比裸指针到处申请了不释放更好。
必须明确:从语义上来说,shared_ptr
代表了一种对生命周期的自动推断。其本质的意义是:A
持有B
的shared_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
,从而使接受事件分发的对象,在生存周期上可以获得彻底销毁的自由,而不和事件分发者产生什么必然的关联。