Item19 Use std::shared_ptr for shared-ownership resource management

​   在上一篇文章中讨论了std::unique_ptr,本文讨论的则是另外一个比较重要的智能指针std::shared_ptr,通过本文标题可知,这是一个共享所有权的智能指针,使用这个智能指针可以实现弱化的垃圾回收(Python的垃圾回收就是基于引用计数机制),C++一直被那些带有gc的编程语言所鄙视,而我觉得C++的智能指针是要比gc更先进的一种机制,因为gc只能帮你管理内存资源,但是资源并不仅仅是内存,还有其它诸如文件fdsocket描述符等,C++通过RAII,和引用计数型智能指针管理所有的资源。

​   从上文中我们已经讨论了shared_ptr的基本概念,接下来我们来谈论一下相关的细节。shared_ptr是一个值语义的模版类,可以任意的复制,拷贝和传递,并自动更新引用计数值,从而控制资源的生命周期。因此,shared_ptr需要保存原始指针和引用计数等信息,所以它的大小是普通指针的两倍,引用计数是使用的原子变量保存的其目的是为了线程安全,而且必须是放在堆内存中,这样在拷贝的时候就可以共享引用计数值了。为了减少不必要的性能损耗在拷贝shared_ptr的时候应该尽可能的使用std::move进行移动,而不是拷贝构造。

​   接下来我们谈论下定制删除器的问题,在上一篇文章中说到std::unique_ptr可以自定义删除器,不过会导致智能指针的大小变大,而这个问题在shared_ptr身上是不存在的,究其原因是因为对于shared_ptr来说,自定义删除器的信息和引用计数的信息是放在一起的称之为控制块,在shared_ptr中有一个指针指向这个控制块,这个指针的大小是不会因为自定义删除器而改变的,如下图: 这里写图片描述

​ 最后谈论的是enable_shared_from_this这个模版类,在讲这个话题之前,先来看一个例子:

std::vector<std::shared_ptr<Widget> processedWidget;
class Widget {
  public:
    ...
    void process();
    ...
};
void Widget::process() {
  processedWidget.emplace_back(this);   //这里存在问题
}

​   上面这段代码是有潜在的问题的,究其原因在于上文中提到的控制块,对于一个资源来说只会创建一个控制块,然后所有的shared_ptr都指向这个控制块,如果创建了两个就会导致未定义的行为,例如下面这段代码:

auto pw = new Widget;
std::shared_ptr<Widget> spw1(pw);
std::shared_ptr<Widget> spw2(pw);

​​   上面的代码spw1spw2分别针对资源pw创建了一个控制块,这在析构的时候会导致资源pw被释放两次,同理,在上面的代码中processedWidget.emplace_back(this)会导致创建一个shared_ptr指向this指针,并创建一个控制块管理这块资源。如果外部也有一个shared_ptr指向this指针,并创建了一个控制块,就会导致未定义的行为(重复释放两次资源),例如下面的代码:

class Widget;
std::vector<std::shared_ptr<Widget>> processedWidget;
auto loggingDel = [](Widget *pw) {
  delete pw; 
};  
class Widget {
  public:
    void process();
};
void Widget::process()  {
  processedWidget.emplace_back(this, loggingDel); //这里存在问题
}
int main() {
  {
    std::shared_ptr<Widget> w(new Widget, loggingDel);
    w->process();
  }
  return 0;
}

​   上面的代码会出现未定义的行为,为了解决这个问题,所以就引入了enable_shared_from_this这个话题,先来看下它的用法:

class Widget;
std::vector<std::shared_ptr<Widget>> processedWidget;
auto loggingDel = [](Widget *pw) {
  delete pw;
};
class Widget : public std::enable_shared_from_this<Widget> {
  public:
    void process();
};
void Widget::process()  {
  processedWidget.emplace_back(shared_from_this());
}
int main() {
  {
    std::shared_ptr<Widget> w(new Widget, loggingDel);
    w->process();
  }
  return 0;
}

​   上面的代码就不存在double free的问题了,在使用shared_from_this返回this指针的shared_ptr的时候会先搜索当前对象的控制块,如果有就不会再创建控制块了。所以也就没有上面提到的问题,如果没有则会出现未定义行为,会抛出异常。

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值