《Effective Modern C++》学习笔记之条款十九:使用std::shared_ptr管理具备共享所有权的资源

std::shared_ptr(共享式智能指针)通过引用计数的方式实现资源共享,且当引用计数为0时,资源将被释放。

和std::unique_ptr不同的是,其不能处理数组,只能处理单个对象;而且std::shared_ptr自定义析构器不属于std::shared_ptr型别的一部分,其尺寸也和析构器没有任何关系。

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

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

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

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,否则将导致该内存被析构多次,导致程序崩溃
  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置空,其引用计数不变。

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

#include 
#include 
 
class Bad
{
public:
	std::shared_ptr getptr() {
        // 裸指针初始化,重新生成一个控制块
	    return std::shared_ptr(this);
	}
	~Bad() { std::cout << "Bad::~Bad() called" << std::endl; }
};
 
int main()
{
	// bp1有自己的控制块,指针指向 new Bad()的返回值,即对象的this指针
	std::shared_ptr bp1(new Bad());
    
    // bp2也有自己的控制块,指针也指向 new Bad()返回的this指针
	std::shared_ptr 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对象一次申请,两次释放,导致程序崩溃

《Effective Modern C++》学习笔记之条款十九:使用std::shared_ptr管理具备共享所有权的资源_第1张图片

《Effective Modern C++》学习笔记之条款十九:使用std::shared_ptr管理具备共享所有权的资源_第2张图片

所以为了处理这个场景,C++11为我们新增了一个类模板:std::enable_shared_from_this,这个模板类提供了一个成员函数share_from_this(),该函数的主要作用是查询当前对象的控制块,如果存在则创建一个指涉到该控制块的std::share_ptr对象,否则,将抛出异常,具体行为未定义,所以为了保证不抛出异常,请在使用该函数前,确保分配的对象已经与一个std::shared_ptr相关联。上面的代码也可以改成这样:

#include 
#include 

//继承于enable_shared_from_this
struct Good : std::enable_shared_from_this // 注意:继承
{
public:
	std::shared_ptr getptr() {
                //使用shared_from_this(),确保接收方的控制块与源对象是同一个
		return shared_from_this();
	}
	~Good() { std::cout << "Good::~Good() called" << std::endl; }
};

int main()
{
	// 大括号用于限制作用域,这样智能指针就能在system("pause")之前析构
	{
                //先让gp1与std::shared_ptr关联,生成控制块
		std::shared_ptr gp1(new Good());

                //gp2和gp1使用的是同一个控制块,所以计数+1
		std::shared_ptr gp2 = gp1->getptr();

		// gp1和gp2的引用计数都为2
		std::cout << "gp1.use_count() = " << gp1.use_count() << std::endl;
		std::cout << "gp2.use_count() = " << gp2.use_count() << std::endl;
	}
	system("pause");
}

《Effective Modern C++》学习笔记之条款十九:使用std::shared_ptr管理具备共享所有权的资源_第3张图片

你可能感兴趣的:(《Effective,Modern,C++》读书笔记,c++,shared_ptr,智能指针,共享指针,unique_ptr)