C++知识点 -- 智能指针

这篇具有很好参考价值的文章主要介绍了C++知识点 -- 智能指针。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++知识点 – 智能指针


一、智能指针的使用及原理

1.使用场景

C++知识点 -- 智能指针
对于上面的场景,p1和p2在new申请空间后,div函数如果出现了除0错误,那么程序就会抛出异常,跳到接受异常的程序段继续执行,p1和p2申请的空间就没有被正常释放,造成了内存泄漏;
这种场景我们就可以使用智能指针来解决空间的释放问题。

2.RAII

RAII(Resource Acquisition Is Initialization)获取到资源立即初始化,是一种利用对象生命周期来控制程序资源的技术;
在对象构造时获取资源,接着控制对资源的访问使其在对象的生命周期内都有效,最后在对象析构时释放资源,我们实际上把管理一份资源的责任托管给了一个对象,好处在于:
(1)不需要显式地释放资源;
(2)采用这种方式,对象所需的资源在其生命周期内始终有效;

3.智能指针的设计思想

(1)利用RAII的思想设计delete资源的类;
(2)像指针一样的行为;

因此,智能指针实际上是一个对象,这个对象重载了operator->和operator*,具有像指针一样的行为;

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		: _ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

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

private:
	T* _ptr;
};

上面的代码就是一个简易的智能指针,能够实现资源的释放,以及像指针一样的行为;

double Div(int a, int b)
{
	if (b == 0)
	{
		throw invalid_argument("除0错误");
	}
	return (double)a / b;
}

void test1()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);

	cout << Div(2, 0) << endl;

}

int main()
{
	try
	{
		test1();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

使用智能指针对上面的代码进行重写,让智能指针托管开辟的新资源,当sp1和sp2析构时,其析构函数会delete其管理的资源;
C++知识点 -- 智能指针

4.智能指针的拷贝问题

编译器自动生成的拷贝构造函数对于内置类型会完成浅拷贝,这里的拷贝需要的就是浅拷贝,因为深拷贝会将管理的资源在其他地方拷贝一份,违背了功能需求;
但是浅拷贝又带来了delete多次造成程序崩溃的问题,因此c++库中设计了几种智能指针来解决拷贝问题;

二、auto_ptr

这是c++98版本中提供的智能指针,auto_ptr对于拷贝的处理是管理权转移
下面是它的模拟实现:

	template <class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			: _ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& ap)
			: _ptr(ap._ptr)
		{
			//管理权转移
			ap._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			//检测是否为自己给自己赋值
			if (this != &ap)
			{
				//释放当前对象中的资源
				if (_ptr)
				{
					delete _ptr;
				}

				//转移ap中的资源到当前对象
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		T* operator->()
		{
			return _ptr;
		}

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

	private:
		T* _ptr;
	};

auto_ptr对于拷贝的处理方式是管理权转移,对于拷贝构造,auto_ptr将资源的管理权转移给新的对象,原来的对象的指针置空;
对于赋值重载,auto_ptr清空等号左边的对象的资源,然后将等号右边的对象管理的资源转交给左边的对象,右边对象的指针置空;

auto_ptr对于智能指针拷贝的处理不是很好,所以很多项目都会禁止使用auto_ptr;

三、unique_ptr

c++11中开始提供unique_ptr,对拷贝的处理方式就是禁止拷贝
下面是它的模拟实现:

	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			: _ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		T* operator->()
		{
			return _ptr;
		}

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

		unique_ptr(const unique_ptr<T>& up) = delete;//只声明不实现
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

	private:
		T* _ptr;
	};

unique_ptr将拷贝构造和赋值重载都进行了只声明不实现的操作,这样类中就不会生成默认的拷贝构造和赋值重载了,也就禁止了unique_ptr对象间的拷贝;

四、shared_ptr

shared_ptr支持拷贝,原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源:
(1)每个资源都维护着一份计数,用来记录该份资源被几个对象共享;
(2)在对象被销毁时,对象引用计数减一;
(3)如果引用计数是0,就说明自己是最后一个被销毁的对象,就必须释放该资源;
(4)如果引用计数不是0,就不能释放该资源;

1.模拟实现

如何实现多个对象共享一个引用计数呢?不能将引用计数设为static成员变量,因为这样所有的资源对象都是用一个引用计数;可以将引用计数的成员便变量设置成一个指针,只在对象构造的时候new一个引用计数,在拷贝和赋值的时候,只增加引用计数;

	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(new int(1)) // 只有在对象调用构造函数的时候,才会新建一个管理资源的引用计数
		{}

		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount) // 拷贝构造的时候不新建引用计数,只拷贝资源指针和计数指针,并且计数++
		{
			*(_pCount)++;
		}

		T* operator->()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}
		
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			--(*_pCount); //指针指向新的资源,原来指向的资源计数需要--

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

	private:
		T* _ptr;

		//引用计数
		int* _pCount;
	};

上面代码中的赋值重载是有问题的:
C++知识点 -- 智能指针
在这种情况下,sp1,2,3全部都赋值sp5,原先sp1,2,3指向的资源的引用计数就减为了0,但是上面的赋值重载函数并不能将原先的资源释放掉,就造成了内存泄露;
同时,还需要解决自己给自己赋值的问题(这里还要注意指向同一份资源之间的对象相互赋值);

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pCount;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr == sp._ptr) //判断指向的资源是不是同一块资源
			{
				return *this;
			}

			Release();

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

整体模拟实现如下:

	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(new int(1)) // 只有在对象调用构造函数的时候,才会新建一个管理资源的引用计数
		{}

		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount) // 拷贝构造的时候不新建引用计数,只拷贝资源指针和计数指针,并且计数++
		{
			*(_pCount)++;
		}

		T* operator->()
		{
			return _ptr;
		}

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

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pCount;
			}
		}

		~shared_ptr()
		{
			Release();
		}
		
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr == sp._ptr) //判断指向的资源是不是同一块资源
			{
				return *this;
			}

			Release();

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

		int use_count()
		{
			return *_pCount;
		}

		T* get()
		{
			return _ptr;
		}

	private:
		T* _ptr;

		//引用计数
		int* _pCount;
	};

2.shared_ptr的循环引用

C++知识点 -- 智能指针
如上图所示,这种情况下,左右两个节点都由shared_ptr n1、n2管理,节点中的指针同样也是shared_ptr,左边节点的next指向右边节点,右边节点的prev指向左边节点,也就是next管着右边节点的内存块,prev管着左边节点的内存块,那么左右两个节点的引用计数都是2;
如果仅仅析构n1和n2,那么左右两节点的引用计数都会减为1,不会触发资源的回收,因为还有next和prev在指向着资源;只有在左边节点被释放,调用了析构函数时,next才会被释放,右边节点的引用计数才能减到0,才能够释放,显然无法直接释放左边的节点;

为了解决shared_ptr的循环引用问题,c++11引入了weak_ptr

五、weak_ptr

weak_ptr不是常规的智能指针,没有RAII,不直接管理资源,weak_ptr主要用shared_ptr构造,用来解决shared_ptr的循环引用问题;
C++知识点 -- 智能指针
weak_ptr访问资源时,不增加引用计数,将节点成员的指针next和prev设置成weak_ptr就不存在循环引用的问题了;

模拟实现

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			: _ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			: _ptr(sp.get())
		{}

		weak_ptr(const weak_ptr<T>& wp)
			: _ptr(wp._ptr)
		{}

		weak_ptr<T>& operator=(const weak_ptr<T>& wp)
		{
			_ptr = wp._ptr;
			return *this;
		}

		T* operator->()
		{
			return _ptr;
		}

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

	private:
		T* _ptr;
	};

六、定制删除器

如果自定义类型使用new [ ]申请空间,使用delete释放可能会出错;
C++知识点 -- 智能指针
delete[]是在开空间时多开4byte的空间在头部,储存new的对象的个数,在delete[]的时候就知道要调用多少次析构函数了;
shared_ptr和unique_ptr都支持定制删除器

C++知识点 -- 智能指针
shared_ptr需要在调用构造函数初始化时传一个仿函数对象或者lambda表达式;
unique_ptr需要传模板参数为仿函数;

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		cout << "delete:" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "delete:" << ptr << endl;
		free(ptr);
	}
};

int main()
{
	//调用构造函数时传仿函数对象
	std::shared_ptr<int> n1(new int[5], DeleteArray<int>());
	std::shared_ptr<int> n2((int*)malloc(5 * sizeof(int)), Free<int>());

	//调用构造函数时传lambda表达式
	std::shared_ptr<int> n3(new int[5], [](int* ptr) {delete[] ptr; });

	//unique_ptr在模板参数中传仿函数
	std::unique_ptr<int, DeleteArray<int>> n4(new int[5]);

	return 0;
}

七、内存泄漏

1.什么是内存泄漏

内存泄漏是指因为疏忽或错误导致程序未能释放已经不再使用的内存的情况;内存泄漏不是内存在物理上消失,而是因为设计错误而失去了对内存的控制(指针丢失);
如果内存还在,进程正常结束,内存也会释放;

2.内存泄露的危害

长期内存泄漏,将导致程序相应越来越慢,直到卡死;

3.如何避免内存泄漏

(1)养成良好的工程编码规范,申请的内存记着去释放;
(2)使用RAII的思想或者智能指针来管理资源;
(3)使用内存泄漏检测工具;
文章来源地址https://www.toymoban.com/news/detail-437503.html

到了这里,关于C++知识点 -- 智能指针的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言】让你不再害怕“指针”【知识点详解】

    目录 一.什么是指针为什么需要指针? 1.什么是指针? 2.为什么需要指针? 以一个代码为例观察地址:(这里我们可以通过调试和打印两种方式观察) 1.调试观察: 2.打印观察(地址是用%p 打印): 二.地址编号的来源指针变量的大小 产生来源 指针变量大小 三.指针类型  

    2024年02月08日
    浏览(50)
  • 人工智能领域的关键知识点

        非常抱歉,由于人工智能涉及的领域非常广泛,50个关键知识点无法详细覆盖所有领域。不过,以下是人工智能领域中的一些关键知识点: 1. 机器学习:机器学习是一种让计算机系统通过学习数据而不是硬编码程序来改善性能的方法。 2. 深度学习:深度学习是机器学习的

    2024年02月08日
    浏览(48)
  • C++碎知识点

    二叉树 由 n个节点构成的形态不同的⼆叉树 同余符号 定义设m是大于1的正整数,a,b是整数,如果m|(a-b),则称a与b关于模m同余,记作abmod(m),读作a同余于b模m。 符号= 按位与 后赋值 C语言中计算优先级 1LL 1LL会在运算时把后面的临时数据扩容成long long类型,再在赋值给左边时转

    2024年02月12日
    浏览(49)
  • 人工智能期末复习——速通知识点

    知识点是通过老师上课ppt整理,对于期末复习的基本考点都有涉及,以及计算题部分都有例题进行讲解,希望能帮助大家更好的复习。 智能的主要流派: 思维理论:智能的核心是思维 知识阈值理论:智能取决于知识的数量及一般化程度 进化理论:用控制取代知识的表示 智

    2024年02月03日
    浏览(51)
  • 论文查重的小知识点 智能写作

    大家好,今天来聊聊论文查重的小知识点 智能写作,希望能给大家提供一点参考。 以下是针对论文重复率高的情况,提供一些修改建议和技巧,可以借助此类工具: 论文查重的小知识点 一、背景介绍 在学术领域,论文查重是保证学术诚信的重要手段。随着学术研究的日益

    2024年01月21日
    浏览(37)
  • 力扣刷题(C++)知识点

    一,找到数组的中间位置 这个是错的,+=不能分开来   C++ vectorint nums 用法 创建一维数组vector: vectorint nums;  //不指定长度 vectorint nums(n);    //指定长度为n   c++ <numeric> accumulate 函数 accumulate函数实现将一段数字从头到尾累加起来 前两个参数是指定累加的范围,第三个参数

    2024年02月13日
    浏览(72)
  • 波奇学C++:多态知识点

    结果是 student 0 原因在于重写时只重写函数的实现,就是说相当于Person的fun的声明和Student的函数实现的拼在一起所以缺省值是0。 如果是子类指针或者引用就不是多态调用了只是单存子类对父类的重定义,隐藏函数。 上一篇文章提到的,多态的本质就是基类和派生类的虚表中

    2024年02月09日
    浏览(41)
  • C++笔记(细碎小知识点)1

    2024年02月08日
    浏览(39)
  • 一些关于c++的琐碎知识点

    目录 bool强转 const构成重载:const修饰*p  移动构造 new int (10)所做的四件事 this指针---为什么函数里面需要this指针? .和-的区别 new创建对象 仿函数 new和malloc的区别 c++系统自动给出的函数有 delete和delete[ ]区别何在 检查有没有析构函数 explict外部 内存泄漏的本质:丢失了内存地

    2024年02月07日
    浏览(49)
  • C++基础知识点整理笔记(四)

    10. C++的内存管理 在C++中,内存被分成五个区:栈、堆、自由存储区、静态存储区、常量区 (一) 栈:存放函数的参数和局部变量,编译器自动分配和释放 (二) 堆:new动态分配的内存,由程序员手动进行释放,否则程序结束后,由操作系统自动进行回收 (三) 自由存储区

    2024年02月15日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包