智能指针的由来和概念
1.由来:我们可以先看下列代码:
int main()
{
int* ptr = new int(10);
return 0;
}
我的上述代码有个明显的问题,那就是new出来的空间没有用delete进行释放,这样造成的结果就是有内存泄漏,由于在写程序中,我这个代码比较短,我们可以直接发现没有释放空间,那如果我们写到很多代码,又很杂,我们有时候会发现不了这些问题,所以我们就推出了一个指针叫做智能指针。 2.智能指针的概念: 可以自动的去管理内存的释放:也就是说,我们申请的空间在作用域结束的时候,我们不需要去调用释放内存的函数就可以将内存释放,防止了内存泄漏。
内存泄漏
上述我们一直在说内存泄漏,那么什么是内存泄漏呢? 1.内存泄漏: 内存泄漏是指因为疏忽,未能将程序开辟的空间在最后不使用的时候得到释放。而内存泄漏并不是物理内存的某段位置丢失,而是因为程序先前在物理内存上分配了空间,但是由于操作的失误,在没有释放这段内存的数据之前失去了对这段内存的控制,因而造成了内存泄漏。 2.内存泄漏的危害: 长期运行的程序出现内存泄漏后危害很大,影响也很大。如操作系统:出现内存泄漏后会慢慢积累,最终就会卡死了。 3.内存泄漏的分类: ①:堆内存泄漏: 堆内存是指,程序在运行中通过malloc/calloc/realloc/new从堆上申请的内存,用完后必须调用相应的free/delete进行释放。假如申请的内存没有得到释放,那么就会出现堆内存泄漏,并且以后这段内存都将无法使用。 ②:系统资源泄漏: 指程序使用系统分配的资源,没有对应的函数进行释放,导致系统资源浪费,严重可导致系统效能减少,系统执行不稳定。 4.如何避免内存泄漏: ①:在内存开辟之后,释放内存。 ②:采用RAII思想或智能指针来处理。 ③:也可也用相应的内存检测工具去检测,然后改正。 总结为以下两点: ①:事前预防。 ②:事后查错。
智能指针的分类及原理
首先我们在学习智能指针之前我们必须搞懂RAII思想是什么。
RAII思想
1.概念:是一种用对象声明周期来控制控制程序资源的一种简单技术。 由于在类中,示例化对象的时候会自动调用类中的构造函数,而对象的作用域结束的时候,又会自动调用类中的析构函数,由于这种操作,让我们有了RAII思想,在对象构造是获取资源,在对象析构时释放资源,这样就很可能的预防了内存泄漏。并且这样作有两大好处:
- 不需要显式的释放资源
- 采用这种方式,对象所需要的资源在其生命周期内始终保持有效。
下面用一段代码演示一下:
class RAII
{
public:
RAII(int *P = nullptr) :a(P)
{}
~RAII()
{
if (a)
{
delete a;
}
}
private:
int* a;
};
(上面这段代码假设是用int的类型进行)
智能指针的分类及原理
1.上述我们实现的例子中,还不能说明其就是一个指针,因为没有重载*,->等一些指针的操作。 进行一些添加,就可以让上述代码具有类似指针的作用:
class RAII
{
public:
RAII(int *P = nullptr) :a(P)
{}
~RAII()
{
if (a)
{
delete a;
}
}
public:
int& operator*()
{
return *a;
}
int* operator->()
{
return a;
}
private:
int* a;
};
这样就具有了指针的一些作用,所以智能指针的主要性质为:
接下来我们说一些库里面的智能指针: 1.auto_ptr智能指针: 实现原理:管理权转移。 意思是,这个智能指针的使用,他不能让两个指针去指向同一个空间,也就是说,一个空间只能让一个智能指针去指向。 下面我们模拟实现一下:
template<class T>
class AutoPtr
{
public:
AutoPtr(T *P = nullptr) :a(P)
{}
AutoPtr(AutoPtr<T>& P) :a(P.a)
{
P.a = nullptr;
}
AutoPtr& operator = (AutoPtr<T>& P)
{
if (&P != this)
{
if (a)
{
delete a;
}
a = P.a;
P.a = nullptr;
}
return *this;
}
~AutoPtr()
{
if (a)
{
delete a;
}
}
public:
T& operator*()
{
return *a;
}
T* operator->()
{
return a;
}
private:
T* a;
};
但是auto_ptr有个缺点就是不能对数组空间进行操作,所以推出了一个auto_array,主要是对数组空间进行操作,其原理和实现和auto_ptr相似。 2.unique_ptr智能指针和scoped_ptr智能指针: ①:unique_ptr智能指针: 在是对atuo_ptr进行了修改,主要的修改是禁止拷贝构造: 代码如下:
template<class T>
class UniquePtr
{
public:
UniquePtr(T *P = nullptr) :a(P)
{}
UniquePtr(UniquePtr<T>& P) :a(P.a)
{
P.a = nullptr;
}
~UniquePtr()
{
if (a)
{
delete a;
}
}
public:
T& operator*()
{
return *a;
}
T* operator->()
{
return a;
}
private:
UniquePtr& operator = (AutoPtr<T>& P);
private:
T* a;
};
将拷贝构造函数设置为私有成员,这样就直接暴力防止了拷贝的发生。 ②:scope_ptr智能指针: 主要也是对auto_ptr智能指针进行了修改,而在unique_ptr的基础上不能进行赋值语句。 代码如下:
template<class T>
class UniquePtr
{
public:
UniquePtr(T *P = nullptr) :a(P)
{}
~UniquePtr()
{
if (a)
{
delete a;
}
}
public:
T& operator*()
{
return *a;
}
T* operator->()
{
return a;
}
private:
UniquePtr& operator = (AutoPtr<T>& P);
UniquePtr(UniquePtr<T>& P);
private:
T* a;
};
直接将赋值语句和拷贝构造函数都移动到私有成员下面,直接进行暴力禁止。 对于这两个指针: ①:unique_ptr拥有一件物品。不可复制但支持所有权转让,它是作为现在不推荐的替代品而引入的。 ②:scoped_ptr是既不可复制也不可移动。当想要确保在超出作用域时删除指针时,这是首选的选择。
3.share_ptr智能指针 share_ptr智能指针,是目前最受欢迎的指针,由于它的实现提供了多个指针指向同一个空间,使这种它的出现可以说是打败了所有目前存储的指针,无敌的存在,下面我们来学习一下share_ptr智能指针: ①:首先,它支持拷贝构造和赋值语句。 ②:原理:是通过引用计数器的方式来实现多个share_ptr对同一个指针进行管理:
- share_ptr内部,给每个资源都有一份计数器,用来记录有几个share_ptr指针共同维护的同一个资源.
- 当有对象被销毁的时候,那么这个计数器就自动减一。
- 如果引用计数器为0,那么便调用相应的析构函数,将这一资源释放掉。
- 如果引用计数器不为0,那就不能释放这一资源,因为还有其他share_ptr智能指针指向这个资源。
简单模拟实现:
template<class T>
class SharePtr
{
public:
SharePtr(T *P = nullptr) : a(P)
, _pRefCount(new int(1))
{}
SharePtr(SharePtr<T>& P) :a(P.a)
{
*_pRefCount++;
}
SharePtr& operator = (SharePtr<T>& P)
{
if (&P != this)
{
if (a)
{
delete a;
}
a = P.a;
}
*_pRefCount++;
return *this;
}
~SharePtr()
{
Release();
}
public:
T& operator*()
{
return *a;
}
T* operator->()
{
return a;
}
public:
void Release()
{
if (--(*_pRefCount) == 0)
{
delete a;
}
}
private:
T* a;
int* _pRefCount;
};
这里面的模拟实现只是对shart_ptr有一个简单的认识,主要是有了引用计数器的引入。这样更好的提供了接口,让智能指针和指针的操作基本相似了。 4.weak_ptr智能指针: weak_ptr智能指针(指针如其名字):为弱指针,这个指针存在功能主要是为了配合share_ptr智能指针,并且不具有指针的操作如:*,->等一些操作。 作用:在与share_ptr使用时,用它声明的指针在指向时不会导致shart_ptr指针中的引用计数器的数量增加,并且它的析构也不会导致引用计数器的减少。(例如在链表的指向问题中。)
|