1. 智能指针
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<memory>
using namespace std;
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
int* p1 = new int[10]; // 这里亦可能会抛异常
int* p2 = new int[10]; // 这里亦可能会抛异常
int* p3 = new int[10]; // 这里亦可能会抛异常
int* p4 = new int[10]; // 这里亦可能会抛异常
try
{
div();
}
catch (...)
{
delete[] p1;
delete[] p2;
delete[] p3;
delete[] p4;
throw;
}
delete[] p1;
delete[] p2;
delete[] p3;
delete[] p4;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
// ...
}
return 0;
}
上面这个解决方法很繁琐
我们引入智能指针
RAII是个指导思想:获取资源之后去初始化一个对象,将资源交给对象管理
能用于智能指针、lock(),unlock()
//SmartPtr.h
namespace tyyg
{
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
: _ptr(ptr)
{}
~SmartPtr()
{
cout << "~SmartPtr()" << endl;
delete _ptr;
}
private:
T* _ptr;
};
}
// test.c
#include "SmartPtr.h"
double div()
{
double a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
tyyg::SmartPtr<int> sp1(new int);
tyyg::SmartPtr<int> sp2(new int);
tyyg::SmartPtr<int> sp3(new int);// sp3的new抛异常,跳到catch的地方,sp1,sp2出作用域调用析构
tyyg::SmartPtr<int> sp4(new int);
tyyg::SmartPtr<pair<string, int>> sp5(new pair<string, int>("sort", 1));
// div()抛异常,sp1,sp2,sp3,sp4出作用域调用析构,不会造成内存泄漏
cout << div() << endl;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
// ...
}
return 0;
}
问题:拷贝
浅拷贝同一块资源会被析构两次,
回忆一下list的迭代器,我们用的是浅拷贝但它没问题,是因为我们不用迭代器负责节点的释放
但我们还是需要浅拷贝来拷贝智能指针,不然就不是管着这块资源
但我们分不清这是别人交给我们管理的,还是别人跟我们一起管理的,这就导致我们不知道该由谁来负责释放资源
如何解决:
1.1 auto_ptr
C++98 auto_ptr 管理权转移,被拷贝对象悬空(交给你管,我不管了)
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
: _ptr(ptr)
{}
~auto_ptr()
{
cout << "~auto_ptr()" << endl;
delete _ptr;
}
// sp2(sp1)
auto_ptr(auto_ptr<T>& sp)
: _ptr(sp._ptr)
{
sp._ptr = nullptr;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
tyyg::auto_ptr<int> sp1(new int);
tyyg::auto_ptr<int> sp2 = sp1;// sp1把资源给sp2管,sp1没用了
// sp1悬空
//*sp1 = 20;// 不能这样写
return 0;
}
boost
C++11
C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr
C++11将boost库中智能指针精华部分吸收了过来
1.2 unique_ptr
核心原理:不让拷贝 --> 拷贝编译就报错
namespace tyyg
{
template<class T>
class unique_ptr
{
public:
// RAII思想
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
//private:
// // sp2(sp1)
// // C++98
// // 1、只声明,不实现
// // 2、声明成私有
// unique_ptr(const unique_ptr<T>& sp);
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
T* _ptr;
};
}
//C++11 unique_ptr
// 核心原理:不让拷贝 --> 拷贝编译就报错
int main()
{
tyyg::unique_ptr<int> up1(new int);
tyyg::unique_ptr<int> up2(up1);// 报错
return 0;
}
1.3 shared_ptr
原理:引用计数(string博客里写过)
count要一个资源一个计数
静态的static int _count不行,因为构造多个独立的对象时只会析构一次
如果是静态的计数,sp3构造时将count改成1,之后sp3析构,count变成0,再往后sp2和sp1指向的资源不释放
只能用int* _count
但也有缺陷,就是多线程的时候_count有可能会加错
namespace tyyg
{
template<class T>
class shared_ptr
{
public:
void Release()
{
if (--(*_pCount) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
// RAII思想
shared_ptr(T* ptr)
:_ptr(ptr)
, _pCount(new int(1))
{}
~shared_ptr()
{
Release();
}
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
{
(*_pCount)++;
}
// sp1 = sp3
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
return *this;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get() const
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
private:
T* _ptr;
int* _pCount;
};
}
//shared_ptr 引用计数,
// 析构的时候计数,最后一个析构对象释放资源
int main()
{
tyyg::shared_ptr<int> sp1(new int);
tyyg::shared_ptr<int> sp2(sp1);
// 静态的count不行,因为构造多个独立的对象时只会析构一次
tyyg::shared_ptr<int> sp3(new int);// 如果是静态的计数,sp3将count改成1,之后sp3析构,count变成0,再往后sp2和sp1指向的资源不释放
sp1 = sp3;
return 0;
}
新问题:循环引用
struct ListNode
{
tyyg::shared_ptr<ListNode> _next = nullptr;
tyyg::shared_ptr<ListNode> _prev = nullptr;
int _val = 0;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
// 循环引用
std::shared_ptr<ListNode> p1(new ListNode);
std::shared_ptr<ListNode> p2(new ListNode);
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
//p1->_next = p2;// p2和_next一起管理它
//p2->_prev = p1;// p1和_prev一起管理它
// 这就导致p1,p2析构的时候这两块空间还未析构,直到_next和_prev析构的时候这两块空间才会析构
//(但_prev析构取决于_next;_next析构取决于_prev,矛盾了)
// 这就导致空间没法释放
return 0;
}
解决方法:别让_prev, _next来管理空间,它们的指向空间时不增加计数,引入weak_ptr来辅助
1.4 weak_ptr
shared_ptr的小弟
专门解决shared_ptr循环引用
weak_ptr拷贝shared_ptr,但不增加计数,
weak_ptr不参与资源管理
namespace tyyg
{
// 不参与指向资源的释放管理
template<class T>
class weak_ptr
{
public:
weak_ptr()
: _ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
: _ptr(sp.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)// 能接收shared_ptr,但不增加计数
{
if (_ptr != sp.get())
{
_ptr = sp.get();
}
return *this;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
struct ListNode
{
tyyg::weak_ptr<ListNode> _next;// 解决循环引用的方法,把这里换成weak_ptr
tyyg::weak_ptr<ListNode> _prev;
int _val = 0;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
// 循环引用
tyyg::shared_ptr<ListNode> p1(new ListNode);
tyyg::shared_ptr<ListNode> p2(new ListNode);
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1->_next = p2;// p2和_next一起管理它
p2->_prev = p1;// p1和_prev一起管理它
// 但p1->_next和p2->_prev都是weak_ptr类型的,能够调用weak_ptr<T>& operator=(const shared_ptr<T>& sp),实现增加指向但不计数
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
return 0;
}
上面的模拟实现只考虑了最基本的情况,跟库里的weak_ptr根本比不了,我们模拟实现只是为了更好地理解它,而不是为了造出更好的轮子
shared_ptr要和weak_ptr一起才能解决循环引用问题!用的时候一定要小心
2. 定制删除器 – 仿函数
unique_ptr/shared_ptr 默认释放资源用的delete
如何匹配申请方式去对应释放呢?回忆一下排序时我们对各种类型排序的处理方法:仿函数
我们先用库里的unique_ptr试一下效果(这里写了一部分仿函数来封装是为了看清楚调用的是哪个函数)
class Date
{
public:
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
// unique_ptr/shared_ptr 默认释放资源用的delete
// 如何匹配申请方式去对应释放呢?
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
template<class T>
struct Free
{
void operator()(T* ptr)
{
cout << "free" << ptr << endl;
free(ptr);
}
};
struct Fclose
{
void operator()(FILE* ptr)
{
cout << "fclose" << ptr << endl;
fclose(ptr);
}
};
int main()
{
std::unique_ptr<Date> up1(new Date);
std::unique_ptr<Date, DeleteArray<Date>> up2(new Date[10]);
std::unique_ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date) * 10));
std::unique_ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));
return 0;
}
然后就自己实现一下吧,其实也就在模板里加一个仿函数,再改一下析构函数
unique_ptr在类的构造参数支持定制删除器,我们的unique_ptr没法在在构造函数传参支持定制删除器,因为通过构造函数传入的仿函数在析构函数里也用不了
namespace tyyg
{
template<class T, class D = default_delete<T>>
class unique_ptr
{
public:
// RAII思想
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
/*cout << "delete" << _ptr << endl;
delete _ptr;*/
D del;// unique_ptr的定制删除器
del(_ptr);
_ptr = nullptr;
}
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
T* _ptr;
};
}