你真的了解智能指针shared_ptr吗?

通常情况下的回答:

shared_ptr是一个类模板,它采用引用计数器,允许多个shared_ptr指向同一个对象,所以也称它为可以共享所有权的智能指针。当我们调用构造函数时,引用计数器会加一;调用析构函数时,引用计数器会减一;当我们使用operator = 时,若不是自赋值情况,所管理的原对象引用计数减一,新对象引用计数加一...当引用计数为零时,所管理的对象会被真正释放。

你可能不了解的底层实现:

我们知道 shared_ptr 是通过引用计数器来实现的,但你知道它的底层是如何实现的吗?

实际上,shared_ptr持有两个指针!,一个是它所【存储的指针】;另一个是指向【控制块】的指针,那么...控制块有包含哪些东西呢?

控制块

控制块是一个动态分配对象,它包含【指向管理对象的指针】,删除器,分配器,拥有所管理对象的shared_ptr的数目(也就是引用计数)以及引用所管理对象的weak_ptr数目

当一个shared_ptr与另一个shared_ptr共享一份对象时,实际上是通过第一个指针指向【所管理的对象】,第二个指针指向【控制块】以实现共享对象所有权的目的

 为什么控制块也要持有【指向管理对象的指针】?

实际上,shared_ptr直接持有的指针就是我们通过 get( ) 方法返回的指针;而控制块所持有的指针,是指向我们所管理的对象的。通常情况下,两者是一致的,但也有特殊情况。这不是胡诌,在cppreference中提到:

“The pointer held by the shared_ptr directly is the one returned by get(), while the pointer or object held by the control block is the one that will be deleted when the number of shared owners reaches zero. These pointers are not necessarily equal.”

于是我有两个疑问:

  1. 为什么一个shared_ptr需要两个指针,一个由它自己直接持有,另一个由控制块持有?
  2. 为什么这两个指针会不相等

怀着疑问,我在shared_ptr的构造函数中找到了答案:

template< class Y >
shared_ptr( const shared_ptr<Y>& r, T *ptr );

这是在shared_ptr构造函数中出现的,我们可以看见第一个参数为管理对象类型为Y 智能指针,第二个参数指向T类型的指针。

这个构造函数的设计是为了,让我们可以有一个shared_ptr指向它所拥有其他东西

看似有些拗口,我们从例子来看:

#include<iostream>
#include<memory>

using std::cout;
using std::endl;

std::shared_ptr<int> creator2()
{
    using Pair = std::pair<double, int>;

    std::shared_ptr<Pair> p(new Pair(3.14, 42));
    std::shared_ptr<int> q(p, &(p->second));
    cout << "q.get() " << q.get() << endl; 
    cout << "p.get() " << p.get() << endl; 
    cout << "q owns <int> value in " << &(p->second) << endl;
    return q;
}

int main() {
    // shared_ptr<Base2> ptr1 = creator1();
    shared_ptr<int> ptr2 = creator2();
    cout << *ptr2 << endl;
    return 0;
}

这是一个有些“矫揉造作”的例子,但是很清晰。在 creator2 中,我们有一个类型为 pair<double, int>的对象,并且有一个shared指针 p 管理它

正常情况下,一旦函数结束,这个pair对象就会被释放,但是由于q和p共享它的所有权,所以指针q保持了pair对象的活性。实际上,我们采用这种方式构造的目的是:我们想让shared指针q指向它【所拥有的】(pair对象)的【其他东西】(int部分)那么当shared指针 q 析构后,它所拥有的 【pair对象】也必须被释放,那么【指向pair对象的指针】应该被【传递给删除器】,也就是说我们需要把它存储在控制块中!

这回答了第一个疑问 “为什么一个shared_ptr需要两个指针,一个由它自己直接持有,另一个由控制块持有?”,实际上,第二个疑问 “为什么这两个指针会不相等” 的答案也呼之欲出了!

上述程序的输出结果我贴在下方:

​
// 输出!
q.get() 0x623f88
p.get() 0x623f80
q owns <int> value in 0x623f88
42

​

这印证了 “有些时候,shared_ptr【直接持有】的指针 和 【控制块持有】的指针不同 ”。

总结

总得来说,shared_ptr 【调用get方法】返回的指针就是它【直接持有】的指针;

而当引用计数为0时,真正【传递给删除器】的指针是【控制块持有】的指针!

最后,找到的一些关于上述所提到构造函数的历史渊源。(英翻中未必准确)

Peter Dimov、Beman Dawes和Greg Colvin通过第一份库技术报告(称为TR1)提议将shared_ptr和weak_ptr纳入标准库。该建议被接受,并最终在2011年的迭代中成为C++标准的一部分。

在这个提案中,作者指出了 "共享指针异化 "的用法。

高级用户经常要求能够创建一个shared_ptr实例q,与另一个(主)shared_ptr p共享所有权,但指向一个不是【*p】的基类对象。例如,p 可能是 q 的【一个成员或一个元素】。本节提出了一个额外的构造函数,可以用于这个目的。

所以他们在控制块中增加了一个额外的指针。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
shared_ptr是一种智能指针,它是C++11引入的一种新特性。shared_ptr的设计思路是利用三个主要组件来实现智能指针的功能:一空间、计数器和锁。 首先,shared_ptr通过一堆空间来存储所指向的对象。这个堆空间是由shared_ptr自动管理的,当最后一个指向对象的shared_ptr被销毁时,这空间会被释放。 其次,shared_ptr使用一个计数器来记录当前有多少个shared_ptr共享同一个对象。每当有一个新的shared_ptr指向对象时,计数器就会加1;当有一个shared_ptr被销毁时,计数器就会减1。当计数器为0时,表示没有任何shared_ptr指向对象,此时对象会被释放。 最后,shared_ptr还使用锁来确保多线程环境下的安全访问。由于shared_ptr是可以被多个指针共享的,因此在多线程环境中,可能存在多个线程同时操作同一个shared_ptr。为了避免竞争条件,shared_ptr内部使用了锁来保证多线程操作的原子性和线程安全性。 总结起来,shared_ptr实现了智能指针的功能,通过一空间、计数器和锁来管理所指向对象的生命周期。它可以跟踪对象的引用计数,并在最后一个shared_ptr被销毁时自动释放对象。同时,shared_ptr还具备多线程安全的特性,可以在多线程环境下使用。 参考文献: 前面介绍的auto_ptr和unique_ptr都存在着些许的缺陷,显得不是那么的“智能”,下面我们来看一下较为智能的shared_ptr的设计思路(一空间、计数器、锁) 主要介绍了C++11新特性之智能指针,包括shared_ptr, unique_ptr和weak_ptr的基本使用,感兴趣的小伙伴们可以参考一下 这是因为weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使weak_ptr指向对象,对象也会被释放。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值