shared_ptr知识点总结

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 一样。

  • 一个 shared_ptr 对象实体可被多个线程同时读取

  • 两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;

  • 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

考虑一个简单的场景,有 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 涉及两个成员的复制,这两步拷贝不会同时(原子)发生。

  1. 复制ptr指针
  2. 复制ref_count指针,导致引用计数+1

一开始,各安其事。

sp5

线程 A 执行 x = g; (即 read g),以下完成了步骤 1,还没来及执行步骤 2。这时切换到了 B 线程。

sp6

同时编程 B 执行 g = n; (即 write g),两个步骤一起完成了。

g重指会使以前的cnt - 1或至资源析构

先是步骤 1:

sp7

再是步骤 2:

sp8

这时 Foo1 对象已经销毁,x.ptr 成了空悬指针!

最后回到线程 A,完成步骤 2:

sp9

描述:

当有一个智能指针复制另一个智能指针时,只复制了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++ 是不保证参数求值顺序, 以及内部表达式的求值顺序的, 所以可能的执行顺序如下:
  1. new Lhs(“foo”))
  2. new Rhs(“bar”))
  3. std::shared_ptr
  4. std::shared_ptr
    好了, 现在我们假设在第 2 步的时候, 抛出了一个异常 (比如 out of memory, 总之, Rhs 的构造函数异常了), 那么第一步申请的 Lhs 对象内存泄露了. 这个问题的核心在于, shared_ptr 没有立即获得裸指针.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值