一.智能指针作用
C++的智能指针主要作用是为了防止内存泄漏。在代码中我们new出来的对象都需要delete,但是当我们我们忘记或者代码出现异常导致没有delete对象,就会产生内存泄漏。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
下面我们看看一个常见的因为异常导致内存泄漏的例子:
#include<iostream>
using namespace std;
static int sa = 1;
int div_func(int a, int b)
{
if (b == 0)
{
throw invalid_argument("除0错误");
}
return a / b;
}
int Func1()
{
int* a1 = new int{1};
int* a2 = new int{sa};
int n=div_func(*a1,*a2);
sa--;
delete a1;
delete a2;
return n;
}
int main()
{
try
{
while (1)
{
int n = Func1();
cout << n;
}
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
运行结果:
main函数调用func1函数,func中new出了a1,和a2,然后调用div_func,当b=0,此时就会抛出异常,异常被mian函数捕获直接跳转,此时new出来的a1和a2就不会被delete,导致内存泄漏。这种代码的内存泄漏,还是比较难防备,此时就需要使用智能指针。
二.智能指针的使用及原理
我们首先介绍一下什么是RAII。
RAII(Resource Acquisition Is Initialization)(资源获取即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
- 1.不需要显式地释放资源。
- 2.对象所需的资源在其生命期内始终保持有效。
智能指针也就是利用RAII的原理实现的,把管理一份资源的责任托管给了一个对象,通过构造函数获取资源,通过析构函数释放资源,看代码:
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
:
_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
这样我们就能通过类的生命周期来对资源进行管理和释放。对于最开始的代码我们只需要把 int* a1 = new int{1};int* a2 = new int{sa}代码写成SmartPtr<int > sp1=new{1};SmartPtr<int >sp2=new{sa},这样即使因为抛异常跳转到mian()函数也会因为生命周期的结束自动释放资源。上面的代码就是智能指针的基本原理,但是它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。下面我们来模拟实现一下常见的几种智能指针。
三.简单模拟实现std::auto_ptr
namespace bit
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
{
}
//移交管理权
auto_ptr(auto_ptr<T>& ap)
{
_ptr = ap.get();
ap._ptr = nullptr;
}
//释放原来的,接收管理权
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~auto_ptr()
{
delete _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
}
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
cout << "调用构造函数" << endl;
}
~Date()
{
cout << "调用析构" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
bit::auto_ptr<int> ap1 = new int{ 1 };
bit::auto_ptr<Date> ap2 = new Date{ 1,1,1 };
cout << *ap1 << endl;
cout << ap2->_year << endl;
bit::auto_ptr<Date> ap3 = ap2;
}
运行结果:
最后一行时的监视窗口:
auto_ptr作为智能指针,当调用拷贝构造或赋值运算符重载,不允许多个智能指针指向同一个对象,而是将一个智能指针的资源管理权移交给另外一个智能指针,这种做法是不太好的,这意味着,赋值后的原auto_ptr对象将不再拥有指针的所有权,其内部指针会被置为NULL。这种行为可能导致一些潜在的错误,因为程序员可能期望原对象仍然拥有指针的所有权。很多公司明确要求不能使用auto_ptr。
四.简单模拟实现std::unique_ptr
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理。
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{
}
unique_ptr (unique_ptr& up) = delete;
unique_ptr<T>& operator=(unique_ptr& up) = delete;
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
通过删除拷贝构造函数和赋值运算符重载来确保指向该资源的只有该智能。也是一个资源智能有一个智能指针。
五.简单模拟实现std::shared_ptr
上面的俩种指针之所以只能做到一个资源智能有一个智能指针,是因为没有解决多个智能指针指向一份资源从而导致重复析构的问题而shared_ptr可以解决这个问题。
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。所有智能指针都指向同一个内存和引用计数。
当有智能指针指向内存资源时,同时让共享的引用计数++,当智能指针析构时,只是让引用计数--,只有当引用计数为0时再调用指向资源的析构函数。
具体看代码:
namespace bit
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pRefCount(new int(1))
, _pmtx(new mutex)
{}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pmtx(sp._pmtx)
{
AddRef();
}
void Release()
{
_pmtx->lock();
bool flag = false;
if (--(*_pRefCount) == 0 && _ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pRefCount;
flag = true;
}
_pmtx->unlock();
if (flag == true)
{
delete _pmtx;
}
}
void AddRef()
{
_pmtx->lock();
++(*_pRefCount);
_pmtx->unlock();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pmtx = sp._pmtx;
AddRef();
}
return *this;
}
int use_count()
{
return *_pRefCount;
}
~shared_ptr()
{
Release();
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pRefCount;
mutex* _pmtx;
};
还需要注意的如果是多线程的话,因为所有指针共享一个引用计数,对引用计数必须加锁访问。
好了,智能指针就讲解到这了,感觉有帮助的话,请点点赞吧,这真的很重要。