【C++深度解析】25、C++中的智能指针

动态内存管理经常出现问题:一种是忘记释放内存,会造成内存泄漏;引用释放的内存,产生引用非法内存的指针。引入智能指针解决上面的问题,自动释放所指向的对象。shared_ptr 允许多个指针指向同一个对象,unique_ptr 则“独占”所指向的对象。weak_ptr 是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。

1 shared_ptr

1.1 shared_ptr 用法

  • 使用 make_shared 函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的 shared_ptr。
  • 使用普通指针调用成员函数创建。
shared_ptr<int> p3 = make_shared<int>(42);

shared_ptr<int>pp(new int(10));

每个 shared_ptr 都有一个引用计数,我们拷贝一个 shared_ptr,计数加 1。当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁时,计数减 1,当计数器变为 0 时,自动释放所管理的对象。

  • shared_ptr 和 unique_ptr 都支持的操作

在这里插入图片描述

  • shared_ptr 独有的操作

在这里插入图片描述

  • 定义和更改 shared_ptr 的其他方法

在这里插入图片描述
使用注意
1、不要混合使用普通指针和智能指针
如果混合使用的话,智能指针自动释放之后,普通指针有时就会变成悬空指针,当将一个shared_ptr 绑定到一个普通指针时,应该使用 shared_ptr 进行操作,不应该再使用内置指针访问内存。

2、不能直接将指针赋值给 shared_ptr,他们一个是指针,一个是类对象

3、不要使用 get 初始化另一个智能指针或为智能指针赋值

shared_ptr<int> p(new int(42));	//引用计数为1
int *q = p.get();				//正确:但使用q时要注意,不要让它管理的指针被释放
{
    shared_ptr(q);				//未定义:两个独立的share_ptr指向相同的内存
}								//程序块结束,q 被销毁,它指向的内存被释放
int foo = *p;					//未定义,p指向的内存已经被释放了

p 和 q 指向相同的一块内存,由于是相互独立创建,各自的引用计数都是 1,当 q 所在的程序块结束时,q 被销毁,导致内存被释放,这时候 p 就变成一个空悬指针,使用将发生未定义的行为,当 p 被销毁时,这块空间会被二次 delete

1.2 实现原理

我们来看看是怎么实现的:

template<class T>
class SharedPtr					//模拟实现shared_ptr
{
public:
	SharedPtr(T* tmp = nullptr) : _ptr(tmp), count(new int(1)) { }

	~SharedPtr()
	{
		if (--(*count) == 0)
		{
			delete _ptr;
			delete count;
		}
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
	
	SharedPtr(SharedPtr<T>& tmp) : _ptr(tmp._ptr), count(tmp.count)
	{
		(*count)++;
	}
	
	SharedPtr<T>& operator = (SharedPtr<T>& tmp)
	{
		if (_ptr != tmp._ptr)		//排除自己给自己赋值的可能
		{
			if (--(*count) == 0)	//先要判断原来的空间是否需要释放
			{
				delete _ptr;
				delete count;
			}
			_ptr = tmp._ptr;
			count = tmp.count;
			(*count)++;
		}
		return *this;				//考虑连等的可能
	}
private:
	T* _ptr;
	int* count;
};

这里先要说说记录引用计数为什么是指针:为了使各个对象都使用同一个变量标记,所以不能直接用整形记录。有人说那行,用个静态的吧,那也不行,用静态的就真正是所有变量都公用一个变量了.比如指针 a,b指向内存块 A,指针 c,d,e 指向内存块 B,本来 a,b 的引用计数应该是 2,c,d,e 的引用计数应该是 3,要使用静态的就都变成 5 了。这里要注意,所以最好的办法就是使用指针。

1.3 shared_ptr 所导致的循环引用问题

循环引用时会造成内存泄漏,首先我们来了解一下什么是循环引用,这里用链表的例子来实现一下:

struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
 
	ListNode(int x)
		:_data(x)
		, _prev(NULL)
		,_next(NULL)
	{}
	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> cur(new ListNode(1));
	shared_ptr<ListNode> next(new ListNode(2));
	cur->_next = next;
	next->_prev = cur;
	cout << "cur" << "     " << cur.use_count() << endl;
	cout << "next" << "     " << next.use_count() << endl;
	return 0;
}

在这里插入图片描述
C++ 库为了解决这个问题,定义了 weak_ptr,专门用于辅助 shared_ptr 来解决引用计数的问题。当 shared_ptr 内部要监视其他的 shared_ptr 对象时,就采用 weak_ptr,不会使被监视的引用计数增加,且当被监视的对象析构后就自动失效。

2 unique_ptr

2.1 unique_ptr 的使用

某个时刻只能有一个 unique_ptr 指向一个给定对象,unique_ptr 不支持普通的拷贝或赋值操作。

  • unique_ptr 的操作

在这里插入图片描述
虽然我们不能拷贝或者赋值 unique_ptr,但是可以通过调用 release 或 reset 将指针所有权从一个(非const)unique_ptr 转移给另一个 unique_ptr

//将所有权从p1转移给p2,release将p1置为空
unique_ptr<string> p2(p1.release());

//将所有权从p3转移到p2,
unique_ptr<string>p3(new string("Trex"));
p2.reset(p3.release());

2.2 实现原理

unique_ptr 原理:直接把拷贝构造/赋值函数定义为 private,且只声明不实现。类成员无法调用这两个函数。

template<class T>
class UniquePtr					//模拟实现unique_ptr
{
public:
	UniquePtr(T* tmp = nullptr)	: _ptr(tmp)	{ }
	~UniquePtr()
	{
		if (_ptr)
			delete _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
private:
	UniquePtr(const UniquePtr<T>& tmp) = delete;
	UniquePtr<T>& operator=(const UniquePtr<T>& tmp) = delete;
	T* _ptr;
};

3 weak_ptr

weak_ptr 它指向 shared_ptr 管理的对象,weak_ptr 不改变引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。

  • weak_ptr的操作

在这里插入图片描述

4 智能指针是线程安全的吗

  • 对于 unique_ptr,由于只是在当前代码块范围内有效。所以不涉及线程安全的问题。
  • 对于 shared_ptr,多个对象要同时共用一个引用计数变量,所以会存在线程安全的问题,但是标准库实现的时候考虑到了这一点,使用了基于原子操作(CAS)的方式来保证 shared_ptr 能够高效,原子的操作引用计数。

5 小结

1、有拷贝构造/赋值的情况,推荐使用 shared_ptr
2、不需要拷贝构造/赋值的时候,可以使用 unique_ptr
3、类内有访问其他 shared_ptr 对象时,使用 weak_ptr,可以不改变引用计数。

发布了298 篇原创文章 · 获赞 181 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/happyjacob/article/details/104582363