std::shared_ptr通过引用计数的方式实现资源共享,当计数为0时,资源将被释放,和std::unique_ptr不同的是其不能处理数组,只能处理单个对象。

std::shared_ptr的尺寸是裸指针的两倍,因为其内部除了包含了一个指涉到该资源的裸指针,还包含一个指涉到该资源的一个控制块的裸指针,该控制块为动态分配在堆上,内容包含:引用计数、弱计数、自定义删除器、自定义分配器等。控制块的生成遵循以下几个规则:

  1. std::make_shared(参见条款21)总是创建一个控制块。std::make_shared会生产出一个新对象,所以此时新对象一定没有控制块的存在

  2. 从具备专属所有权的指针(std::auto_ptr、std::unique_ptr)出发构造的std::shared_ptr时,会创建一个控制块。因为std::auto_ptr或std::unique_ptr不存在控制块,转换为std::shared_ptr后,需要新建

  3. 当std::shared_ptr构造函数使用裸指针作为实参构造时,会创建一个控制块。因为裸指针也不含有控制块,所以我们不能使用同一个裸指针构造两个std::shared_ptr,否则将导致程序崩溃.可以使用new代替。

  4. 当std::shared_ptr被std::shared_ptr或std::weak_ptr作为实参构造时,不会创建控制块,因为传入的std::shared_ptr或std::weak_ptr已经含有控制块

我们在对std::shared_ptr的引用计数进行操作时,其成本是很高的,因为这个是原子操作。所以当我们对一个std::shared_ptr进行移动构造时,实际上是非常迅速的,因为这个操作仅仅是将源std::shared_ptr置空,其引用计数不变。

与std::unique_ptr类似,std::shared_ptr也可以自定义析构器,且析构器的大小不会影响std::shared_ptr的大小,因为函数是分配在堆上的,方式上与std::unique_ptr差别也比较大的,代码示例如下:

//自定义析构器myDelete1 
auto myDelete1 = [](Widget* w){
    cout<<"myDelete1"<<endl;
};

//自定义析构器myDelete2
auto myDelete2 = [](Widget* w){
    cout<<"myDelete2"<<endl;
};

//pw1的自定义析构器为myDelete1
std::shared_ptr<Widget> pw1(new Widget,myDelete1);

//pw2的自定义析构器为myDelete2
std::shared_ptr<Widget> pw2(new Widget,myDelete2);

//虽然pw1与pw2的自定义析构函数不一样看,但都是std::shared_ptr<Widget>类型,可以相互转换
//而如果是std::unique_ptr则不行,因为其类型不同
pw1 = pw2;

下面我们再来考虑一种场景,如果类Bad被std::share_ptr管理,且在类Bad的成员函数里需要把当前类对象作为share_ptr参数传给其他对象进行初始化时,将会发生什么呢?代码如下:

#include <memory>
#include <iostream>
 
class Bad
{
public:
    std::shared_ptr<Bad> getptr() {
            //裸指针初始化,重新生成一个控制块
        return std::shared_ptr<Bad>(this);
    }
    ~Bad() { std::cout << "Bad::~Bad() called" << std::endl; }
};
 
int main()
{
    // bp1有自己的控制块,指针指向 new Bad()的返回值,即对象的this指针
    std::shared_ptr<Bad> bp1(new Bad());
    
        // bp2也有自己的控制块,指针也指向 new Bad()返回的this指针
    std::shared_ptr<Bad> bp2 = bp1->getptr();

    // 打印bp1和bp2的引用计数
    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
    std::cout << "bp2.use_count() = " << bp2.use_count() << std::endl;
}  // this对象一次申请,两次释放,导致程序崩溃