目录
智能指针示例:bit::SmartPtr sp1(new int);
一.智能指针的使用及原理
1.RAII
智能指针示例:bit::SmartPtr<int> sp1(new int);
解释:无论sp1,sp2,sp3,sp4这四个哪里的new出异常,都没问题,比如sp3的new抛异常了,sp1和sp2照样可以成功释放;如果sp1的new抛异常了,则直接跳到main的catch中结束;如果函数div抛异常了,sp1,sp2,sp3,sp4照样可以成功释放;
class SmartPtr
{
public:
// RAII思想
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout <<"delete"<<_ptr << endl;
//delete[] _ptr;
delete _ptr;
_ptr = nullptr;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* Get()
{
return _ptr;
}
private:
T* _ptr;
};
double div()
{
double a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
bit::SmartPtr<int> sp1(new int);
bit::SmartPtr<int> sp2(new int);
bit::SmartPtr<int> sp3(new int);
bit::SmartPtr<int> sp4(new int);
bit::SmartPtr<pair<string, int>> sp5(new pair<string, int>("sort", 1));
*sp1 = 0;
sp5->second++;
cout << div() << endl;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
// ...
}
return 0;
}
2.auto_ptr——C++探索路上的失败品
sp2=sp1进行拷贝构造,使sp2指向sp1的资源,随后直接把sp1置空了,由sp2接管了sp1的资源,导致sp1无法进行访问,即:管理权转移,被拷贝的对象悬空。很多公司明确要求,不能使用auto_ ptr
template<class T>
class auto_ptr
{
public:
// RAII思想
auto_ptr(T* ptr)
:_ptr(ptr)
{}
~auto_ptr()
{
if (_ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
}
// sp2(sp1)
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
// ...
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
bit::auto_ptr<int> sp1(new int);
bit::auto_ptr<int> sp2 = sp1;
// sp1悬空
// *sp1 = 10;
*sp2 = 20;
return 0;
}
3.unique_ptr
unique_ptr的实现原理:因为auto_ptr拷贝有风险,所以unique_ptr直接没风险了:简单粗暴的防止拷贝/不让拷贝,拷贝编译就报错。
unique_ptr仅仅只是把拷贝构造和拷贝赋值进行删除:=delete 作用是删除该函数,使该函数不可调用
以前C++98的方式:已放弃的方式
//private:
// // sp2(sp1)
// //
// // 1、只声明,不实现
// // 2、声明成私有
// unique_ptr(const unique_ptr<T>& sp);
C++11的方式:
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
unique_ptr总实现
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;
};
int main()
{
bit::unique_ptr<int> up1(new int);
//bit::unique_ptr<int> up2(up1);
bit::unique_ptr<int> up2(new int);
//up1 = up2;
return 0;
}
4.shared_ptr
shared_ptr:核心原理就是引用计数,记录几个对象管理这块资源,析构的时候一一计数,最后一个析构的对象释放资源
为了使每个资源都有一个计数,在构造函数中定义一个_pCount(new int(1)) ,拷贝构造时就 (*_pCount)++。如果*_pCount不为0,说明有多个对象指向这块资源,只需 --(*_pCount)即可;析构时只让最后一个指向资源的对象释放,即:*_pCount=0时进行释放,这样防止了多次析构;
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()
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;
};
int main()
{
bit::shared_ptr<int> sp1(new int);
bit::shared_ptr<int> sp2(sp1);
bit::shared_ptr<int> sp3(new int);
sp1 = sp1;
sp1 = sp2;
sp1 = sp3;
sp2 = sp3;
return 0;
}
二.shared_ptr的致命缺点-循环引用
1.循环引用
我们让p1和p2这两个节点互相指向:p1的_next指向p2,p2的_prev指向p1
这样会出现一些问题:因为原本是p1管理第一个节点,p2管理第二个节点。互相指向后,p1和p2的_prev共同管理第一个节点;互相指向后,p2和p1的_next共同管理第二个节点;这样导致这两个节点的管理计数都是2。出main函数时,p1对象在后面,则先析构,因为管理计数是2,所以计数减1,p2对象析构时,管理计数也是2,所以也是计数减1。
①p2受最后一个指向资源的对象释放,这个对象就是_next,所以p2如果想释放,就要依赖p1的_next析构,但是_next是p1对象的成员变量,_next想析构需要p1对象这个节点释放;
②p1如果想释放,就要依赖p2的_prev析构,但是_prev是p2对象的成员变量,_prev想析构需要p2这个对象释放;但是p2又需要p1的_next析构……又回到①号问题了。这就叫做循环引用
板书介绍(跟上面一样,可以不看):
_prev 管着左边的节点
_next 管着右边的节点
他们分别是两个节点自定义成员
节点析构释放,_prev/_next这些里面的成员才析构释放
左边节点释放, next析构
_prev析构, 左边节点释放
右边节点释放,prev析构
右边节点释放,依赖_next
为了解决循环引用,需要用到weak_ptr:
2.weak_ptr
辅助shared_ptr解决循环引用。
使对象只指向资源,不管理资源(就是不需要释放资源)
作用:std: :weak_ptr<ListNode> _next; p1->_next=:p2; 可以使_next不参与管理,即:可以访问,但是资源的释放还是p2释放,_next不参与释放资源,_next指向p2管理的资源时并不会增加计数。
(use_count()用于查看shared_ptr的计数个数)
weak_ptr实现代码
——————————————————————————————————————.h文件
// 不参与指向资源的释放管理
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)
{
if (_ptr != sp.get())
{
_ptr = sp.get();
}
return *this;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
public:
T* _ptr;
};
——————————————————————————————————————test.cpp
struct ListNode
{
/*ListNode* _next = nullptr;
ListNode* _prev = nullptr;*/
//bit::shared_ptr<ListNode> _next = nullptr;
//bit::shared_ptr<ListNode> _prev = nullptr;
bit::weak_ptr<ListNode> _next;
bit::weak_ptr<ListNode> _prev;
int _val = 0;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
// 循环引用
bit::shared_ptr<ListNode> p1(new ListNode);
bit::shared_ptr<ListNode> p2(new ListNode);
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1->_next = p2;
p2->_prev = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
return 0;
}
三.定制删除器
unique_ptr 第二个参数是删除器,由于缺省参数默认是 default_delete<T> ,default_delete<T>中就是delete,但是如果你需要析构[]用delete会报错,malloc出来的也不能用delete,等等,所以我们要定制删除器:bit::unique_ptr<Date, DeleteArray<Date>> up2(new Date[10]);
DeleteArray<Date> 写一个DeleteArray 的类并重载operator(),operator()内部自己定制释放方式,删除器参数传仿函数类型,就叫定制删除器
cpp文件
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()
{
// 传类型 21:01继续
bit::unique_ptr<Date> up1(new Date);
bit::unique_ptr<Date, DeleteArray<Date>> up2(new Date[10]);
bit::unique_ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date)* 10));
bit::unique_ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));
// 传对象
std::shared_ptr<Date> sp1(new Date[10], DeleteArray<Date>());
std::shared_ptr<Date> sp2(new Date[10], [](Date* ptr){delete[] ptr; });
return 0;
}
std::shared_ptr<Date> sp1(new Date[10], DeleteArray<Date>()); 可以直接构造传匿名对象
.h文件
template<class T>
struct default_delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
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; //定制删除器
del(_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;
};