C++ 智能指针的简单原理
所引用的头文件 : #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*
,那么在他析构的时候需要做两件事:
- 释放最初申请的那部分空间
- 调用析构函数完成清理工作
那么问题就是出现在了析构函数中,我们自定义一个类,打印析构函数的情况(dev编译器下这种情况不会报崩溃的错误,vs直接崩掉)
也就是说,对于自定义对象数组的指针,我们析构的时候如果是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)