在上一篇文章中讨论了std::unique_ptr
,本文讨论的则是另外一个比较重要的智能指针std::shared_ptr
,通过本文标题可知,这是一个共享所有权的智能指针,使用这个智能指针可以实现弱化的垃圾回收(Python
的垃圾回收就是基于引用计数机制),C++
一直被那些带有gc的编程语言所鄙视,而我觉得C++
的智能指针是要比gc
更先进的一种机制,因为gc
只能帮你管理内存资源,但是资源并不仅仅是内存,还有其它诸如文件fd
,socket
描述符等,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);
上面的代码spw1
和spw2
分别针对资源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的时候会先搜索当前对象的控制块,如果有就不会再创建控制块了。所以也就没有上面提到的问题,如果没有则会出现未定义行为,会抛出异常。