C++ 智能指针的简单原理

本文探讨了C++智能指针的必要性,详细解释了内存泄漏问题和如何通过RAII原则避免。文章介绍了不同类型的智能指针,如auto_ptr、unique_ptr和shared_ptr的工作原理,以及weak_ptr的用途。此外,还讨论了删除器和RAII设计的守卫锁在确保资源正确管理中的角色。
摘要由CSDN通过智能技术生成

所引用的头文件 : #include<memory>

为什么会有智能指针的原因

下面这个代码,当主函数调用Fun()的时候,在Fun函数内部使用了new 出来一个数组,new的作用和C语言中的malloc差不多,都是在堆中开辟相应大小的空间。

但是堆中开辟的空间是需要我们手动去释放的,不然就会出现内存泄漏的问题

#include "vld.h"
#include "head.hpp"

void Fun()
{
   
	int* arr = new int[10];
}

int main()
{
   
	Fun();
	return 0;
}

在这里插入图片描述

delete引起的内存泄漏

有时候加了delete却还是会出现内存泄漏,但是不加delete却又不会出现内存泄漏

  • 加了delete却还是会出现内存泄漏
void Fun()
{
   
	int* arr = new int[100];

	delete arr;
	//delete[] arr;
}

对于基本的数值类型,delete 和 delete[]可以说是没有区别的,因为new在开辟空间的时候,会记录所开辟空间的大小,然后delete的时候可以正确释放内存。

这是由于数值类型没有析构函数,所以在delete的时候,不需要调用析构函数来释放其他的指针,但是对于C++中的自定义对象,就像string,当使用new申请了自定义对象类型的数组string*,那么在他析构的时候需要做两件事:

  1. 释放最初申请的那部分空间
  2. 调用析构函数完成清理工作

在这里插入图片描述
那么问题就是出现在了析构函数中,我们自定义一个类,打印析构函数的情况(dev编译器下这种情况不会报崩溃的错误,vs直接崩掉)

dev编译器编译情况
也就是说,对于自定义对象数组的指针,我们析构的时候如果是delete arr,那么造成的结果就是new所开辟的空间被释放了,但是我们在对象中的清理函数却没有全部执行,只是执行了0号下标的析构函数(因为arr 即为 0号下标的地址)。

在这里插入图片描述
这样,当我们自定义对象中还存在new的对象后,还是会存在内存泄漏的,所以对于自定义类型的对象,需要使用delete[],在释放空间的同时,逐一调用每个对象的析构函数
在这里插入图片描述

  • 不加delete却又不会出现内存泄漏
int main()
{
   
	int* arr = new int[100];

	return 0;
}

在这种情况下,arr数组的作用域是main函数的开始到结束,也就是生命周期是跟随整个进程的。当函数结束的时候,操作系统会自动回收掉,也就不算内存泄漏了。

所以对于这种忘记delete以及delete不完全的问题,C++有个智能指针的概念,就是用一个类对指针进行封装,这样出了作用域后,就会自动调用这个类的析构函数,不用我们手动去delete了。

内存泄漏以及内存泄漏的危害:C&C++内存管理(如何检测内存泄漏)

智能指针的使用及其原理

RAII

RAII(Resource Acquisition Is Initialization),也称为资源获取就是初始化,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。(百度百科)

在这里插入图片描述
就是利用了面向对象中封装的特性,构造即初始化,析构即释放。将指针的两个过程封装在一个类中,让他的初始化和释放不用我们操心,都交给类去完成。

auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针,他的设计思想不是很完美,主要就是一个权限转移的原理。

我们在使用指针的时候,会有一些拷贝的操作

int* pa = new int;
int* pb = pa;

这时候需要我们在类中重载=,我们一般的拷贝函数,就是把这个指针的地址拷贝过去,让两个类中的指针指向同一块地址,也就是这样的

		// = 赋值  有BUG的写法
		auto_ptr<T>& operator=(auto_ptr<T>& obj){
   
			if (_ptr != obj._ptr){
   
				_ptr = obj._ptr;
			}
			return *this;
		}

但是这样有个BUG,就是析构函数的调用,因为两个类并不相同,所以作用域结束的时候,调用析构函数时,会对同一块地址调用两次析构函数,也就是调用了两次delete,就会导致越界访问的

在这里插入图片描述
所以,C++98的auto_ptr智能指针中,做出了一个改变,那就是发生拷贝后,进行权利转移
在这里插入图片描述
也就是说,当发生拷贝之后,原本的智能指针就不能再指向之前的地址了,而是被置为了nullptr。保证了同一个对象,只能同时由一个智能指针进行管理。这样的设计有点不靠谱

之后我们还需要重载*->

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

		~auto_ptr()
		{
   
			if (_ptr != nullptr){
   
				delete _ptr;
				_ptr = nullptr;
			}
		}

		//拷贝函数,使用权限转移的思想
		auto_ptr(auto_ptr<T>& obj)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值