Vue框架:
从项目学Vue
OJ算法系列:
神机百炼 - 算法详解
Linux操作系统:
风后奇门 - linux
int div(){
int a, b;
cin >>a >>b;
if(b == 0)
throw invalid_argument("除0错误");
return a/b;
}
void func(){
int *p = new int;
cout<<div() <<endl;
delete p;
}
int main(){
try{
func();
}catch(const exception &e){
cout<< e.what() <<endl;
}
return 0;
}
int div(){
int a, b;
cin >>a >>b;
if(b == 0)
throw invalid_argument("除0错误");
return a/b;
}
void func(){
int *p = new int;
try{
cout<<div() <<endl;
}catch(const exception &e){
cout<< e.what() <<endl;
}
delete p;
}
int main(){
try{
func();
}
catch(const exception &e){
cout<< e.what() <<endl;
}
return 0;
}
多次为指针申请空间,之后统一释放。
当一次申请出错时,前面所有申请的资源无法释放:
int main(){
int *p1 = new int;
int *p2 = new int;
int *p3 = new int;
delete p1;
delete p2;
delete p3;
return 0;
}
int main(){
int *p1 = nullptr;
int *p2 = nullptr;
int *p3 = nullptr;
try{
int *p1 = new int;
int *p2 = new int;
int *p3 = new int;
}catch(...){
if(p2 == nullptr)
delete p1;
if(p3 == nullptr){
delete p1;
delete p2;
}
}
delete p1;
delete p2;
delete p3;
return 0;
}
RAII:resource acquisition is initialization
好处:
实质:
一个成员变量是模板指针的类
模板指针指向new函数的返回指针
资源无效后析构时释放模板指针所指资源
template <class T>
class smart_ptr{
private:
T *ptr;
public:
smart_ptr(T *_ptr){
ptr = _ptr;
}
T& operator*(){
return *ptr;
}
T* operator->(){
return ptr;
}
//有bug的析构函数
~smart_ptr(){
cout<<ptr <<" " <<*ptr <<endl;
delete ptr;
}
}
int main(){
smart_ptr<int> sp1(new int);
smart_ptr<int> sp2(new int);
smart_ptr<int> sp3(new int);
//析构函数bug体现:同一空间被析构两次,肯定报错
int *p = new int;
smart_ptr<int> sp4 = p;
smart_ptr<int> sp5 = p;
cout<<div() <<endl;
return 0;
}
template <class T>
class auto_ptr{
private:
T *ptr;
public:
auto_ptr(T *_ptr){
ptr = _ptr;
}
auto_ptr(auto_ptr<T> &ap){
ptr = ap.ptr;
ap.ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T> *ap){
if(this != &ap){
if(ptr)
delete ptr;
ptr = ap.ptr;
ap.ptr = nullptr;
}
return *this;
}
T& operator*(){
return *ptr;
}
T* operator->(){
return ptr;
}
~auto_ptr(){
delete ptr;
}
};
优点:
直接了当的解决了双重析构的问题
缺点1:
新的auto_ptr到来会覆盖旧的auto_ptr
对于auto_ptr使用不熟悉的同学可能出这样的错
int main(){
auto_ptr<int> sp1(new int);
auto_ptr<int> sp2(sp1);
*sp2 = 10;
cout<< *sp2 <<endl;
cout<< *sp1 <<endl;
return 0;
}
缺点2:
管理权转移只存在于auto_ptr对象之间拷贝
当存在两个auto_ptr对象都从一个指针初始化而来时,还是会出现双重析构问题:
int main(){
int *p = new int(10);
auto_ptr<int> ap1(p);
auto_ptr<int> ap2(p);
return 0;
}
template <class T>
class unique_ptr{
private:
T *ptr;
//c++11中delete关键字屏蔽函数
unique_ptr(unique_ptr<T> const &) = delete;
unique_ptr& operator=(unique_ptr<T> const &) = delete;
//c++98中私有 + 只声明不实现
unique_ptr(unique_ptr<T> const &);
unique_ptr& operator=(unique_ptr<T> const &);
public:
unique_ptr(T *_ptr){
ptr = _ptr;
}
~unique_ptr(){
delete ptr;
}
void Show(){
cout<<*ptr <<endl;
}
};
理论上每个指针都只能存在一个unique_ptr对象
当不采用unique_ptr对象拷贝赋值,
而是直接使用指针初始化两个unique_ptr对象时,还是存在双重析构
int *p = new int(10);
unique_ptr<int> uq1(p);
unique_ptr<int> uq2(p);
template <class T>
class shared_ptr{
private:
static int refCount;
T *ptr;
public:
shared_ptr(T *_ptr){
refCount = 0;
ptr = _ptr;
}
shared_ptr(auto_ptr &ap){
refCount++;
ptr = ap.ptr;
}
~shared_ptr(){
refCount--;
if(refCount == 0 && ptr){
delete ptr;
}
}
};
int main(){
shared_ptr<int> sp1(new int);
shared_ptr<int> sp2(sp1);
shared_ptr<int> sp3(sp1);
shared_ptr<int> sp4(new int);
return 0;
}
应该是每个资源独立使用一个自己的计数器
如果所有资源都使用同一个引用计数器,那么会产生如下结果
初始化时:
析构时:
sp1 & sp2 & sp3都对同一块内存析构,引发多重析构异常
int *p = new int(10);
shared_ptr<int> sp1(p); //静态计数器refCount == 0
shared_ptr<int> sp2(p); //静态计数器refCount == 0
/*
析构时直接双重析构,异常
*/
静态引用计数器本身存储漏洞,且不能解决指针直接构造对象问题,
下面来看看动态引用计数器能不能同时解决两个问题?
template <class T>
class shared_ptr{
private:
T *ptr;
int *refCount; //动态引用计数器
public:
shared_ptr(T *_ptr){
ptr = _ptr;
refCount = (new int(1));
//从这步开始已经决定了智能指针只能走对象拷贝路线,不能走指针直接构造路线
}
shared_ptr(const shared_ptr<T> &sp){
ptr = sp.ptr;
refCount = sp.refCount;
(*refCount)++;
}
shared_ptr<T>& operator=(const shared_ptr<T> &sp){
if(ptr != sp.ptr){
ptr = sp.ptr;
refCount = sp.refCount;
(*refCount)++;
}
}
~shared_ptr(){
if(--(*refCount) == 0 && ptr){
delete ptr;
delete refCount;
}
}
};
int main(){
shared_ptr<int> sp1(new int);
shared_ptr<int> sp2(sp1);
shared_ptr<int> sp3(sp1);
shared_ptr<int> sp4(new int);
return 0;
}
每个资源独立使用自己的计数器
不同的资源计数器之间互不干扰
class shared_ptr{
shared_ptr<T>& operator=(const shared_ptr<T> &sp){
if(ptr != sp.ptr){
ptr = sp.ptr;
refCount = sp.refCount;
(*refCount)++;
}
}
};
class shared_ptr{
shared_ptr<T>& operator=(const shared_ptr<T> &sp){
if(ptr != sp.ptr){
//原资源引用数--
if(--(*refCount) == 0){
delete ptr;
delete refCount;
}
//先资源引用数++
ptr = sp.ptr;
refCount = sp.refCount;
(*refCount)++;
}
}
};
吸收动态引用计数的两大缺点之后,
我们终于可以写出基本没有安全问题的智能指针类了:
#include
#include
using namespace std;
template <class T>
class SharedPtr{
private:
T *ptr;
int *refCount;
mutex *mtx;
private:
void AddRefCount(){
mtx.lock();
*refCount++;
mtx.unlock();
}
void SubRefCount(){
bool flag = 0;
mtx.lock();
if (--(*refCount) == 0){
delete ptr;
delete refCount;
flag = true;
}
mtx.unlock();
if(flag == 1)
delete mtx;
}
public:
SharedPtr(T *_ptr){
ptr = _ptr;
refCount = new int(0);
mtx = new mutex;
}
//默认采用拷贝构造的对象暂无ptr/refCount/mtx
SharedPtr(SharedPtr<T> &sp){
ptr = sp.ptr;
refCount = sp.refCount;
mtx = sp.mtx;
AddRefCount();
}
//默认采用赋值构造的对象已有ptr/refCount/mtx
SharedPtr<T>& operator=(SharedPtr<T> &sp){
if(ptr != sp.ptr){
SubRefCount();
ptr = sp.ptr;
refCount = sp.refCount;
mtx = sp.mtx;
AddRefCount();
}
}
~ShardPtr(){
SubRefCount();
}
};
类的基本优点:指针所指资源不用时自动析构释放
多个智能指针对象共享一块资源:
每一块资源都独立使用一个引用计数器
直接指针构造 / 拷贝构造 / 赋值构造 / 析构,都不会出现多重析构
线程安全:不会出现引用计数器少++ / 少- -的情况
int *p = new int(10);
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p);
shared_ptr<int> sp3(p);
struct ListNode{
int data;
shared_ptr<ListNode> prev;
shared_ptr<LIstNode> next;
~ListNode(){
cout<<"~ListNode()"<<endl;
}
}
int main(){
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->next = node2;
node2->next = node1;
return 0;
}
析构一个节点需要析构三部分
当前两块资源的引用计数器状态:
要释放node1,需要资源引用计数器==0
node1完成refCount–后,refCount==1
要想refCount继续–,需要释放node2的next
要释放node2的next,需要释放node2
要释放node2,需要资源引用计数器==0
node2完成refCount–后,refCount==1
要想refCount继续–,需要释放node1的prev
要想释放node1的prev,需要释放node1
发现出现了类似锁套锁的死锁情况,不过这里叫循环引用
下面来看c++11中解决循环引用的weak_ptr<>
node1->next = node2;
node2->next = node1;
//各自资源引用计数器数目 +1 了
struct ListNode{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode(){
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
return 0;
}
不论是功能完整的shared_ptr还是特殊场景下的weak_ptr,
既然称为智能指针,就可以接收很多类型的指针
但是不同类型指针所指资源释放方式不同:
所以我们在单纯为这些智能STL类传入指针的同时,也应该传入删除指针的方式,这就叫做定制删除器
删除功能本质还是一个可调用对象:
重提一下new delete 和 new[] delete[]的区别:
template <class T>
class default_delete{
public:
void operator()(const T*ptr){
cout<<"delete:"<<ptr<<endl;
delete ptr;
}
};
template <class T, class D = default_delete<T>>
class del_ptr{
private:
T *ptr;
public:
unique_ptr(T *_ptr){
ptr = _ptr;
}
~unique_ptr(){
if(ptr){
D del;
del(ptr);
}
}
};
struct DeleteArray{
void operator()(A* ptr){
cout<< "delete[] : "<<ptr <<endl;
delete[] ptr;
}
};
struct DeleteFile{
void operator()(FILE* ptr){
cout<< "fclose[] : "<<ptr <<endl;
fclose(ptr);
}
};
int main(){
del_ptr<A> sp1(new A); //默认删除器
del_ptr<A, DeleteArray> sp2(new A[10]);
del_ptr<A, DeleteFile> sp3(fopen("test.txt", "r"));
}
struct DeleteArray{
void operator()(A* ptr){
cout<< "delete[] : "<<ptr <<endl;
delete[] ptr;
}
};
int main(){
unique_ptr<A> sp1(new A);
unique_ptr<A, DeleteArray> sp1(new A);
return 0;
}
int main(){
unique_ptr<A> sp1(new A[10], [](A* p){
delete[] p;
},);
unique_ptr<A> sp2(fopen("test.txt","r"), [](FILE *p){
fclose(p);
});
}