智能指针

原理:

  • 智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。
  • 动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源
  • 所以智能指针不用手动去释放内存

常用的智能指针:

1、shared_ptr:
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源

  • 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针
  • 每次创建类的新对象时,初始化指针并将引用计数置为1
  • 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数
  • 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数
  • 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)

2、unique_ptr

  • unique_ptr 指针指向的堆内存无法同其它 unique_ptr 共享
  • 每个 unique_ptr 指针都独自拥有对其所指堆内存空间的所有权。
  • 每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收。
  • 所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中

3、weak_ptr

  • 该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用
  • 借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。
  • 它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数
  • 如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放
  • 所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针
  • expired函数:判断指针所指的内存空间是否被释放掉/指针是否为空/
  • lock成员函数:返回一个shared_ptr类型的指针

智能指针的作用

  • 方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
  • shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
  • 初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。
  • 不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr p4 = new int(1);的写法是错误的
  • 同上

说说你了解的auto_ptr作用

  • auto_ptr的出现,主要是为了解决“有异常抛出时发生内存泄漏”的问题;
    • 抛出异常,将导致指针p所指向的空间得不到释放而导致内存泄漏;
  • auto_ptr构造时取得某个对象的控制权,在析构时释放该对象。
  • 不能直接将一般类型的指针赋值给auto_ptr类型的对象
  • 由于auto_ptr对象析构时会删除它所拥有的指针,所以使用时避免多个auto_ptr对象管理同一个指针;
  • 析构函数中删除对象用的是delete而不是delete[],所以auto_ptr不能管理数组
  • T* get(),获得auto_ptr所拥有的指针;T* release(),释放auto_ptr的所有权,并将所有用的指针返回

智能指针的循环引用
循环引用

  • 使用多个智能指针share_ptr时,出现了指针之间相互指向,从而形成环的情况,有点类似于死锁
  • 这种情况下,智能指针往往不能正常调用对象的析构函数,从而造成内存泄漏
#include <iostream>
using namespace std;

template <typename T>
class Node
{
public:
	Node(const T& value)
		:_pPre(NULL)
		, _pNext(NULL)
		, _value(value)
	{
		cout << "Node()" << endl;
	}
	~Node()
	{
		cout << "~Node()" << endl;
		cout << "this:" << this << endl;
	}

	shared_ptr<Node<T>> _pPre;
	shared_ptr<Node<T>> _pNext;
	T _value;
};

void Funtest()
{
	shared_ptr<Node<int>> sp1(new Node<int>(1));
	shared_ptr<Node<int>> sp2(new Node<int>(2));

	cout << "sp1.use_count:" << sp1.use_count() << endl;
	cout << "sp2.use_count:" << sp2.use_count() << endl;

	sp1->_pNext = sp2; //sp2的引用+1
	sp2->_pPre = sp1; //sp1的引用+1

	cout << "sp1.use_count:" << sp1.use_count() << endl;
	cout << "sp2.use_count:" << sp2.use_count() << endl;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}
//输出结果
//Node()
//Node()
//sp1.use_count:1
//sp2.use_count:1
//sp1.use_count:2
//sp2.use_count:2
  • 应该尽量避免出现智能指针之前相互指向的情况,如果不可避免,可以使用使用弱指针——weak_ptr
  • weak_ptr,它不增加引用计数,只要出了作用域就会自动析构。

智能指针出现循环引用怎么解决?

  • 弱指针用于专门解决shared_ptr循环引用的问题
  • weak_ptr不会修改引用计数,即其存在与否并不影响对象的引用计数器

手写实现智能指针类需要实现哪些函数?

  • 一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。
  • 它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。
  • 一个构造函数、拷贝构造函数、复制构造函数、析构函数、移动函数;
template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* ptr = NULL):_ptr(ptr), _pcount(new int(1))
	{}

	SharedPtr(const SharedPtr& s):_ptr(s._ptr), _pcount(s._pcount){
		(*_pcount)++;
	}

	SharedPtr<T>& operator=(const SharedPtr& s){
		if (this != &s)
		{
			if (--(*(this->_pcount)) == 0)
			{
				delete this->_ptr;
				delete this->_pcount;
			}
			_ptr = s._ptr;
			_pcount = s._pcount;
			*(_pcount)++;
		}
		return *this;
	}
	T& operator*()
	{
		return *(this->_ptr);
	}
	T* operator->()
	{
		return this->_ptr;
	}
	~SharedPtr()
	{
		--(*(this->_pcount));
		if (*(this->_pcount) == 0)
		{
			delete _ptr;
			_ptr = NULL;
			delete _pcount;
			_pcount = NULL;
		}
	}
private:
	T* _ptr;
	int* _pcount;//指向引用计数的指针
};
posted @ 2023-02-23 11:04  小秦同学在上学  阅读(69)  评论(0编辑  收藏  举报