shared_ptr的安全性
- shared_ptr的引用计数本身是线程安全(引用计数是原子操作)。
- 多个线程同时读同一个shared_ptr对象是线程安全的。
- 如果是多个线程对同一个shared_ptr对象进行读和写,则需要加锁。
- 多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护。
weak_ptr的安全性
weak_ptr的引入作用
weak_ptr引入的作用是对shared_ptr的补充,主要解决shared_ptr里面的循环引用造成无法释放内存的问题。
eg:shared_ptr内部维护了一个共享的引用计数器,多个shared_ptr可以指向同一个资源。如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放。
weak_ptr是什么
weak_ptr 是为了配合shared_ptr而引入的,它指向一个由shared_ptr管理的资源但不影响资源的生命周期。也就是说,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
不论是否有weak_ptr指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放。
weak_ptr更像是shared_ptr的助手而不是智能指针。
weak_ptr在多线程中的安全使用
先说结论:在多线程开发中,使用weak_ptr的expired()接口是不安全的,而lock()接口是安全的。
weak_ptr不控制对象的生命周期,但是,它知道对象是否还活着。
用lock()函数把它可以提升为shared_ptr,如果对象还活着,返回有效的shared_ptr,如果对象已经死了,提升会失败,返回一个空的shared_ptr。
提升的行为(lock())是线程安全的。
#include <iostream>
#include <memory>
class B;
class A
{
public:
std::weak_ptr<B> m_p;
int x;
};
class B
{
public:
std::weak_ptr<A> m_p;
int x;
};
int main()
{
auto p_a = std::make_shared<A>();
auto p_b = std::make_shared<B>();
p_a->m_p = p_b;
p_b->m_p = p_a;
//使用weak_ptr解决循环引用问题
//weak_ptr的使用
if(!p_a->m_p.expired()) //这种使用是不安全的
{
p_a->m_p.lock()->x = 100;
}
std::shared_ptr<A> w_s_p = p_b->m_p.lock(); //这是安全的
if(w_s_p)
{
w_s_p->x = 100;
}
else
{
}
return 0;
}
查看上述代码,在第一次使用weak_ptr的时候使用了expired()接口(if(!p_a->m_p.expired()) 这行),使用完该接口后对A中的m_p进行操作。在多线程开发的过程中,可能expired()接口执行完去执行另外其他的东西了,CPU在这个时候该线程的时间片刚好用完了,去调度另一个线程了。然后当CPU返回到该线程时,可能存在p_a->m_p已经释放掉了,p_a->m_p.lock()这时候已经是一个空指针了,再去调用p_a->m_p.lock()->x显然是非法的,是不安全的。在单线程中是无所吊谓的,但是基本上像这类智能指针一般是使用在多线程的开发当中。
所以对于weak_ptr的使用,不要p_a->m_p.lock()->x这样子的使用,使用下面第二种的方式,将weak_ptr升级为shared_ptr的指针类型,然后判断当时的shared_ptr的指针状态。若指针不为空执行需要的内容,为空return…
在lock()的过程中,该接口是线程安全的。
智能指针的一些注意
- std::make_shared() 接口引入是在C++11中
- std::make_unique() 接口引入是在C++14中
- std::move()也是C++14中
编译时需要注意c++版本 - weak_ptr的expired()可以当做use_count() == 0 使用,效率要大于use_count()
- shared_ptr的引用是计数是原子操作,其操作是安全的