shared_ptr知识点总结
shared_ptr实现(基于map)
template<typename T>
class mshared_ptr {
public:
//构造
mshared_ptr(T *ptr = nullptr);
//析构
~mshared_ptr();
//拷贝构造
mshared_ptr(mshared_ptr<T> &src);
//赋值重载运算符
mshared_ptr& operator = (mshared_ptr<T> &src);
//解引用运算符(指针)
T& operator*();
//成员运算符
T* operator->();
private:
T * _ptr;
static map<T*, int> mp;
};
//静态成员必须在类外初始化,不是某个对象所拥有的
template<typename T>
map<T*, int> mshared_ptr<T>::mp;
template<typename T>
mshared_ptr<T>::mshared_ptr(T *ptr = nullptr) {
_ptr = ptr;
mp.insert(make_pair(ptr,1));
}
template<typename T>
mshared_ptr<T>::~mshared_ptr() {
if (--mp[_ptr] <= 0 && _ptr != null_ptr) {
delete _ptr;
_ptr = nullptr;
mp.erase(_ptr);
}
}
template<typename T>
mshared_ptr<T>::mshared_ptr(mshared_ptr<T> &src) {
_ptr = src._ptr;
mp[_ptr] ++;
}
template<typename T>
mshared_ptr<T>& mshared_ptr<T>::operator=(mshared_ptr &src) {
if (_ptr == src._ptr) {
return *this;
}
//本来指向的指针减一
if (--mp[_ptr] <= 0 && _ptr != null_ptr) {
delete _ptr;
_ptr = nullptr;
mp.erase(_ptr);
}
_ptr = src._ptr;
mp[_ptr] ++;
return *this;
}
template<typename T>
T& mshared_ptr<T>::operator*() {
return *_ptr;
}
template<typename T>
T* mshared_ptr<T>::operator->() {
return _ptr;
}
shared_ptr线程安全级别
陈硕大佬:
shared_ptr 的线程安全级别和内建类型、标准库容器、std::string 一样,即:
引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。shared_ptr 的线程安全级别和内建类型、标准库容器、std::string 一样。
考虑一个简单的场景,有 3 个 shared_ptr 对象 x、g、n:
- shared_ptr g(new Foo); // 线程之间共享的 shared_ptr
- shared_ptr x; // 线程 A 的局部变量
- shared_ptr n(new Foo); // 线程 B 的局部变量
但是 y=x 涉及两个成员的复制,这两步拷贝不会同时(原子)发生。
- 复制ptr指针
- 复制ref_count指针,导致引用计数+1
一开始,各安其事。
线程 A 执行 x = g; (即 read g),以下完成了步骤 1,还没来及执行步骤 2。这时切换到了 B 线程。
同时编程 B 执行 g = n; (即 write g),两个步骤一起完成了。
g重指会使以前的cnt - 1或至资源析构
先是步骤 1:
再是步骤 2:
这时 Foo1 对象已经销毁,x.ptr 成了空悬指针!
最后回到线程 A,完成步骤 2:
描述:
当有一个智能指针复制另一个智能指针时,只复制了ptr,还没有让cnt ++时,此智能指针突然析构(另外一个使用它的智能指针放弃它),但是移动语义可以解决这个问题
weak_ptr
循环依赖问题
from:https://www.learncpp.com/cpp-tutorial/15-7-circular-dependency-issues-with-stdshared_ptr-and-stdweak_ptr/
#include <iostream> #include <memory> // for std::shared_ptr #include <string> class Person { std::string m_name; std::shared_ptr<Person> m_partner; // initially created empty public: Person(const std::string &name): m_name(name) { std::cout << m_name << " created\n"; } ~Person() { std::cout << m_name << " destroyed\n"; } friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2) { if (!p1 || !p2) return false; p1->m_partner = p2;//p2引用计数 = 2; p2->m_partner = p1;//p1引用计数 = 2; std::cout << p1->m_name << " is now partnered with " << p2->m_name << "\n"; return true; } }; int main() { auto lucy = std::make_shared<Person>("Lucy"); auto ricky = std::make_shared<Person>("Ricky"); //创建完成后,lucy 和 ricky 的引用计数各为1, partnerUp(lucy, ricky); return 0; }
问题的解释:
- ricky析构时,ricky->m_partner的引用计数 - 1 = 1,不会销毁ricky->m_partner
- 两次析构中,Lucy和ricky认为自己还有副本,不会调用自身的析构函数。
问题的解决
weak_ptr用于解决”引用计数”模型循环依赖问题,weak_ptr指向一个对象,并不增减该对象的引用计数器。weak_ptr用于配合shared_ptr使用,并不影响动态对象的生命周期,即其存在与否并不影响对象的引用计数器。weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。weak_ptr提供了expired()与lock()成员函数,前者用于判断weak_ptr指向的对象是否已被销毁,后者返回其所指对象的shared_ptr智能指针
weak_ptr的使用
class Person
{
std::string m_name;
std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr
public:
Person(const std::string &name) : m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is now partnered with " << p2->m_name << "\n";
return true;
}
const std::shared_ptr<Person> getPartner() const { return m_partner.lock(); } // use lock() to convert weak_ptr to shared_ptr
const std::string& getName() const { return m_name; }
};
int main()
{
auto lucy = std::make_shared<Person>("Lucy");
auto ricky = std::make_shared<Person>("Ricky");
partnerUp(lucy, ricky);
auto partner = ricky->getPartner(); // get shared_ptr to Ricky's partner
std::cout << ricky->getName() << "'s partner is: " << partner->getName() << '\n';
return 0;
}
enable_shared_from_this类的作用和实现
当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。
1.为何不直接传递this指针
使用智能指针的初衷就是为了方便资源管理,如果在某些地方使用智能指针,某些地方使用原始指针,很容易破坏智能指针的语义,从而产生各种错误。
2.可以直接传递share_ptr么?
不能,因为这样会造成2个非共享的share_ptr指向同一个对象,未增加引用计数导对象被析构两次。
正确的使用方法
struct Person : std::enable_shared_from_this<Person> // 注意:继承
{
public:
std::shared_ptr<Person> getptr() {
return shared_from_this();
}
~Good() { std::cout << "Person::~Persono()" << std::endl; }
};
未增加引用计数导对象被析构两次。
正确的使用方法
struct Person : std::enable_shared_from_this<Person> // 注意:继承
{
public:
std::shared_ptr<Person> getptr() {
return shared_from_this();
}
~Good() { std::cout << "Person::~Persono()" << std::endl; }
};
异常安全
void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),std::shared_ptr<Rhs>(new Rhs("bar")));
C++ 是不保证参数求值顺序, 以及内部表达式的求值顺序的, 所以可能的执行顺序如下:
- new Lhs(“foo”))
- new Rhs(“bar”))
- std::shared_ptr
- std::shared_ptr
好了, 现在我们假设在第 2 步的时候, 抛出了一个异常 (比如 out of memory, 总之, Rhs 的构造函数异常了), 那么第一步申请的 Lhs 对象内存泄露了. 这个问题的核心在于, shared_ptr 没有立即获得裸指针.