下面这段程序在“cout << Divide(len, time) << endl;“可能会抛出异常,如果不捕获这个异常,也就是不加对应的catch语句,那么后面的delete[]就执行不到位,这样就会造成内存泄漏。所以,我们应该加上对应的catch语句,将异常捕获后释放资源,再将异常重新抛出。
除了Divide会抛出异常,new的部分也会抛出异常,若是”int* array1 = new int[10];“处抛异常,倒没什么事,因为抛出异常代表内存申请失败,但若是”int* array2 = new int[10];“处抛异常呢?此时array1已经成功申请内存了,如果不delete掉array1的资源,就会造成内存泄漏,为了避免这样的情况,还需在array2处在写一个try语句。但是当存在多个变量进行new时,代码会变的很搓,所以这里就可以使用到智能指针。
double Divide(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。
// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
// 但是如果array2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
// 是智能指针,否则代码太戳了
int* array1 = new int[10];
int* array2 = new int[10]; // 抛异常呢
try
{
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
catch (...)
{
cout << "delete []" << array1 << endl;
cout << "delete []" << array2 << endl;
//先将异常捕获,释放资源,再重新抛出,这里捕获异常的目的就是为了释放资源,避免内存泄漏
delete[] array1;
delete[] array2;
throw; // 异常重新抛出,捕获到什么抛出什么
}
// ...
cout << "delete []" << array1 << endl;
delete[] array1;
cout << "delete []" << array2 << endl;
delete[] array2;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
RAII是ResourceAcquisition Is Initialization的缩写,本质是一种利用对象⽣命周期来管理获取到的动态资源。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问, 资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常 释放,避免资源泄漏问题。
简单讲就是,申请了内存空间,但这个内存空间不需要自己管理,而是交给一个对象进行管理,当这个对象生命周期结束时,会析构,同时也会把管理资源释放掉。 构造函数保存资源,析构函数释放资源
#include<iostream>
using namespace std;
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;
}
T& operator[](size_t i)
{
return _ptr[i];
}
private:
T* _ptr;
};
double Divide(int a,int b)
{
//如果这里抛出异常,会跳到main函数中去。根据智能指针资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源。所以在跳转到main函数中去之前,会调用智能指针的析构函数将资源释放掉
if (b==0)
{
throw "Divide:分母不能为0";
}
else
{
return ((double)a / (double)b);
}
}
void Func()
{
SmartPtr<int>sp1 = new int[10];
SmartPtr<int>sp2 = new int[10];
SmartPtr<int>sp3 = new int[10];
SmartPtr<pair<int,int>>sp4 = new pair<int, int>[10];
int len, time;
cin >> len >> time;
cout << Divide(len,time) << endl;
sp1[5] = 50;
sp4->first = 1;
sp4->second = 2;
cout << sp1[5] << endl;
}
int main()
{
try
{
Func();
}
catch(const char*errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
智能指针解决了抛出异常可能会导致内存泄漏的问题,但是它自身也存在一些问题。比如:智能指针的拷贝:
template<class T>
class SmartPtr
{
public:
// RAII
SmartPtr(T* ptr)
:_ptr(ptr)
{
}
~SmartPtr()
{
cout << "delete[] " << _ptr << endl;
delete[] _ptr;
}
// 重载运算符,模拟指针的⾏为,⽅便访问资源
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
private:
T* _ptr;
};
int main()
{
//我们自己没有实现拷贝构造,编译器自己生成的默认拷贝构造是浅拷贝
//这样就会导致两个智能指针指向同一块资源,析构时就会析构两次
SmartPtr<int>sp1 = new int[10];
SmartPtr<int>sp2(sp1);
return 0;
}
根据前面所学,当存在申请空间时,就要调用深拷贝。但是智能指针模拟的是原生指针,原生指针1拷贝给原生指针2的目的是赋值,他们指向的资源依然是同一块。智能指针1拷贝给智能指针2的目的是为了让两个智能指针共同管理这块资源
#include<iostream>
#include<memory>
using namespace std;
struct Date
{
public:
Date(int year=1,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{ }
~Date()
{
cout << "析构" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
auto_ptr<Date>ap1(new Date);
//拷贝发生资源管理权的转移,ap1悬空
auto_ptr<Date>ap2(ap1);
//空指针的访问,这也是auto_ptr会存在的问题
//ap1->_year;
return 0;
}
拷贝完后,在对ap1进行访问,这就是对空指针进行访问:
struct Date
{
public:
Date(int year=1,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{ }
~Date()
{
cout << "析构" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
//unique_ptr只支持移动构造,不支持拷贝
unique_ptr<Date>up1(new Date);
//当移动完后,up1会悬空
unique_ptr<Date>up2(move(up1));
return 0;
}
unique_ptr与auto_ptr的区别:前者是告知使用者,自己只支持移动构造,不支持拷贝构造,使用完后会造成指针悬空的情况;后者是告知使用者自己是拷贝构造,但是会造成指针悬空的问题并未告知使用者
struct Date
{
public:
Date(int year=1,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{ }
~Date()
{
cout << "析构" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
shared_ptr<Date>sp1(new Date);
shared_ptr<Date>sp2(sp1);
shared_ptr<Date>sp3(sp1);
//输出引用计数
cout << sp1.use_count() << endl;
sp1->_year++;
cout << sp1->_year << endl;
cout << sp2->_year << endl;
cout << sp3->_year << endl;
//shared_ptr还能像如下这样赋值
shared_ptr<Date>sp4=make_share<Date>(2024,1,1);
return 0;
}
引用计数:当多个对象管理同一块资源时,用一个count记录对象个数,当count==0时,再将资源释放
namespace liu
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T*ptr=nullptr)
:_ptr(ptr)
,_pcount(new int(1))
{ }
~shared_ptr()
{
if (--(*_pcount) == 0)
{
cout << "析构" << endl;
delete _pcount;
delete _ptr;
}
}
shared_ptr(const shared_ptr<T>&sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
{
(*_pcount)++;
}
shared_ptr<T>& operator=(const shared_ptr<T>&sp)
{
if (_ptr==sp._ptr)
{
return *this;
}
if (--(*_pcount)==0)
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
(*_pcount)++;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
};
}
如果以int count的方式来计数:
如果以静态成员变量的方式来计数:
智能指针析构时默认是进⾏delete释放资源,这也就意味着如果不是new出来的资源,交给智能指 针管理,析构时就会崩溃。
//原生指针的空间、malloc来的空间、new[]出来的空间或者其他不是new出来的空间,都不能交给智能指针管理
shared_ptr<Date>sp1(new Date[10]);
面对这种情况,有两种解决方案:
第一种:
//模板特化
shared_ptr<Date[]>sp2(new Date[10]);
第二种:
//定制删除器
struct Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
~Date()
{
cout << "析构" << endl;
}
int _year;
int _month;
int _day;
};
class Fclose
{
public:
void operator()(FILE*ptr)
{
cout << "flcose()" << endl;
fclose(ptr);
}
};
template<class T>
void DeleteArrayFunc(T *ptr)
{
cout << "函数指针" << endl;
delete[] ptr;
}
int main()
{
//传仿函数
shared_ptr<FILE>sp3(fopen("智能指针.cpp","r"),Fclose());
//传lambda
shared_ptr<FILE>sp4(fopen("智能指针.cpp", "r"), [](FILE* ptr) {
cout << "fclose()" << endl;
shared_ptr<Date>sp5(new Date[10], [](Date* ptr) {
cout << "delete[]" << endl;
delete[] ptr; })
shared_ptr<Date>sp6(new Date[10], DeleteArrayFunc<Date>);
fclose(ptr); });
return 0;
}
unique_ptr定制删除器智是在模板处,shared_ptr定制删除器是在构造函数参数处
uniqueptr传定制删除器如果不想传仿函数想以其他形式传如删除器的话,会很麻烦。 sharedptr定制删除器的位置是在函数参数的位置,编译器会自动推导类型。
//unique_ptr不以传仿函数的方式传入定制删除器
//lambda
auto fcloseFunc = [](FILE* ptr) {fclose(ptr); };
unique_ptr<FILE, decltype(fcloseFunc)>up1(fopen("智能指针.cpp", "r"), fcloseFunc);
//函数指针
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
shared_ptr⼤多数情况下管理资源非常合适,⽀持RAII,也⽀持拷贝。但是在循环引用的场景下会 导致资源没得到释放造成内存泄漏,
weakptr不⽀持RAII,也不⽀持访问资源,所以我们看⽂档发现weakptr构造时不⽀持绑定到资 源,只⽀持绑定到sharedptr,绑定到sharedptr时,不增加shared_ptr的引用计数,那么就可以 解决上述的循环引⽤问题。
sharedptr中的count计数在sharedptr释放时不会立即释放,因为它还需要提供给weakptr使用,如果立即释放了,就会造成weakptr野指针的情况。
weak_ptr中还有expired接口来检查资源是否过期。
shared_ptr<string>sp1(new string("11111"));
shared_ptr<string>sp2(sp1);
weak_ptr<string>wp1 = sp1;
cout << wp1.expired() << endl;
cout << wp1.use_count() << endl;
如果shareptr的资源是weakptr所需要的,那么可以使用lock()接口在资源释放前将锁住锁住。
锁住资源实际上就是再用一个shared_ptr指针来管理该资源。