C++ 智能指针—shared_ptr详解

前序内容:

C++ 智能指针—unique_ptr详解(一)

C++ 智能指针—unique_ptr详解(二)

为了实现智能指针在拥有多个实例的运用场景(例如多个线程)仍然可以自动管理内存块,C++11引入了shared_ptr。

shared_ptr初始化方法与unique_ptr基本一样:

#include 
#include 
#include 
class X
{
public:
	X() {
		strcpy_s(buffer, 32, "123456789");
		std::cout << "X
";
	}
	~X() { std::cout << "~X
"; }
	X(const X&other) { std::cout << "enter copy
"; };
	X &operator=(const X&other) { std::cout << "enter =
"; };
	X &operator=(const X&&other) { std::cout << "enter move
"; return *this; };
	X(X&& other) { std::cout << "enter move-con
"; };
	void test()
	{
		std::cout << "enter test
";
	}
	char buffer[32];
};
void closeFile(std::FILE* fp)
{
	std::cout << "Close File.
";
	std::fclose(fp);
}
class MyOperator
{
public:
	MyOperator() = default;
	~MyOperator() = default;

	void operator()(X* x) const {
		std::cout << "operator delete X
";
		delete x;
	};
};
int main()
{
  {
    std::shared_ptr sPtr1(nullptr);  //空指针,不触发析构
  }
  std::cout<<"e.g 1 finish!"< b = std::make_shared();
    //采用new初始化
    std::shared_ptr < X > c(new X);
  }
	std::cout<<"e.g 2 finish!"< a(new X[3]);
  }
	std::cout<<"e.g 3 finish!"< fp(std::fopen("./data.txt", "w"),
        closeFile);
      fprintf(fp.get(), "123
");
    }
	std::cout<<"e.g 4 finish!"< xm(new X, MyOperator());
			auto mop = MyOperator();
			std::shared_ptr xm2(new X, mop);
  }
  std::cout<<"e.g 5 finish!"<

以下是运行结果:

e.g 1 finish!
X
X
~X
~X
e.g 2 finish!
X
X
X
~X
~X
~X
e.g 3 finish!
Close File.
e.g 4 finish!
X
X
operator delete X
~X
operator delete X
~X
e.g 5 finish!

shared_ptr在实际使用时,多个实例指向的内存地址是一致的:

		std::shared_ptra ( new X());
		std::shared_ptr b = a;
		std::cout << "b:		" << static_cast(&b) << "
";
		std::cout << "b.get():	" << static_cast(b.get()) << "
";
		std::cout << "b->buffer:	" << static_cast(b->buffer) << "
";
		std::cout << "a:		" << static_cast(&a) << "
";
		std::cout << "a.get():	" << static_cast(a.get()) << "
";
		std::cout << "a->buffer:	" << static_cast(a->buffer) << "
";

运行结果:

b:              0000007B641EF2F8
b.get():        00000242BF16D1A0
b->buffer:      00000242BF16D1A0
a:              0000007B641EF2C8
a.get():        00000242BF16D1A0
a->buffer:      00000242BF16D1A0

可以看到a和b地址不一样,是两个实例,但是管理的资源地址都是一致的。

shared_ptr如何实现的管理相同的地址?

这是网上查询到的原理性质的实现方式:

#include
#include
#include
using namespace std;

template
class Shared_Ptr{
public:
	Shared_Ptr(T* ptr = nullptr)
		:_pPtr(ptr)
		, _pRefCount(new long(1))
		, _pMutex(new mutex)
	{}
	~Shared_Ptr()
	{
		Release();
	}
	Shared_Ptr(const Shared_Ptr& sp)
		:_pPtr(sp._pPtr)
		, _pRefCount(sp._pRefCount)
		, _pMutex(sp._pMutex)
	{
		AddRefCount();
	}
	Shared_Ptr& operator=(const Shared_Ptr& sp)
	{
		//if (this != &sp)
		if (_pPtr != sp._pPtr)
		{
			// 释放管理的旧资源
			Release();
			// 共享管理新对象的资源,并增加引用计数
			_pPtr = sp._pPtr;
			_pRefCount = sp._pRefCount;
			_pMutex = sp._pMutex;
			AddRefCount();
		}
		return *this;
	}
	T& operator*(){
		return *_pPtr;
	}
	T* operator->(){
		return _pPtr;
	}
	int UseCount() { return *_pRefCount; }
	T* Get() { return _pPtr; }
	void AddRefCount()
	{
		_pMutex->lock();
		++(*_pRefCount);
		_pMutex->unlock();
	}
private:
	void Release()
	{
		bool deleteflag = false;
		_pMutex->lock();
		if (--(*_pRefCount) == 0)
		{
			delete _pRefCount;
			delete _pPtr;
			deleteflag = true;
		}
		_pMutex->unlock();
		if (deleteflag == true)
			delete _pMutex;
	}
private:
	long *_pRefCount;
	T* _pPtr;
	mutex* _pMutex;
};

大致的思路:

管理2个资源,第一个是被管理的资源对应的指针,第二个是计数器指针,计数器是一个长整型变量,初始值为1,当发生复制时,计数器+1,当发生Release或者析构时,计数器-1,当计数器为0,则触发真正的资源释放函数。

查询STL标准代码,采用的是原子操作,而不是示例中的互斥操作,效率更高。详细解析推荐查看:源码分析shared_ptr实现 - 知乎

shared_ptr使用注意点--避免循环引用

想象一下链表的基本单元节点的写法:

struct Node
{
  int data;
  Node *next;
};

将其转换为shared_ptr:

struct Node
{
  int data;
	std::shared_ptr pNext;
	~Node() { std::cout << "~Node" << std::endl; }
};

如果此时使用循环链表:

    auto nodeA = std::make_shared();
		auto nodeB = std::make_shared();
		nodeA->pNext = nodeB;
		std::cout << "A use_count:" << nodeA.use_count() << std::endl;
		std::cout << "B use_count:" << nodeB.use_count() << std::endl;
		nodeB->pNext = nodeA;
		std::cout << "##########" << std::endl;
		std::cout << "A use_count:" << nodeA.use_count() << std::endl;
		std::cout << "B use_count:" << nodeB.use_count() << std::endl;

则nodeA和nodeB都不会资源释放

查询shared_ptr的引用计数,发现再第6行代码后A和B都为2。

A use_count:1
B use_count:2
##########
A use_count:2
B use_count:2

为了避免这种循环引用导致资源不释放的情况,C++11引入了weak_ptr的概念

weak_ptr是shared_ptr的一种弱引用,实际使用时要调用lock函数来获取实际的对象。

将刚才的代码修改为weak_ptr就可以正常释放了

struct Node2
{
	std::weak_ptr pNext;
	~Node2() { std::cout << "~Node2" << std::endl; }
};
int main()
{
		auto nodeA = std::make_shared();
		auto nodeB = std::make_shared();
		nodeA->pNext = nodeB;
		std::cout << "A use_count:" << nodeA.use_count() << std::endl;
		std::cout << "B use_count:" << nodeB.use_count() << std::endl;
		nodeB->pNext = nodeA;
		std::cout << "##########" << std::endl;
		std::cout << "A use_count:" << nodeA.use_count() << std::endl;
    std::cout << "B use_count:" << nodeB.use_count() << std::endl;
}

另外的,当weak_ptr完成资源的捕获后,还能够在原始的shared_ptr被释放后,临时的延长资源的生命周期。

		auto nodeA = std::make_shared();
		auto threadfun = [&nodeA]()
		{
			std::weak_ptr c = nodeA;
			auto getA = c.lock();
			std::cout << "get lock" << std::endl;
			std::this_thread::sleep_for(2s);
			getA->data = 1;
			std::cout << "finish!" << std::endl;
		};
		std::thread t1(threadfun);
		std::this_thread::sleep_for(100ms);
		nodeA.reset();
		std::cout << "get reset" << std::endl;
		t1.join();

在nodeA reset后,由于weak_ptr已经通过lock获取得到资源,所以仍然可以操作,运行结果如下:

get lock
get reset
finish!
~Node2
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章