传统的错误处理机制
1.终止程序
如assert,缺陷:如果发生内存错误,除0错误就直接终止程序
2.返回错误码
缺陷:需要程序员自己去查找对应的错误,如系统的很多库的接口函数都是通过把错误码放到errno中表示错误。
错误码不够清晰,不同程序就得定义一套错误码
c++针对上述的不足,引入异常,即使程序出现错误,但只要正确捕获,就不会影响错误之后的程序运行
double test(double a, double b)
{
if (b == 0)
throw"除0错误";
else
return a / b;
}
int main()
{
while (1)
{
try
{
double d1, d2;
cin >> d1 >> d2;
cout<<test(d1, d2)<<endl;
}
catch (const char* str)
{
cout << str << endl;
}
}
return 0;
}
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
三个关键字
throw"T" catch(T&str)
两个T的类型必须是一样的 上述代码中如果将catch改成catch (const int str)
会报错,因为throw的是一个字符串,那么捕获的也必须是字符串try
{
try
{
try
{
double d1, d2;
cin >> d1 >> d2;
cout << test(d1, d2) << endl;
}
catch (const char* str)
{
cout << str<<"一" << endl;
}
}
catch (const char* s)
{
cout << s <<"二"<< endl;
}
}
catch (const char* st)
{
cout << st << endl;
}
此时与throw匹配的是cout << str<<"一" << endl;
有可能没有匹配的catch,此时会终止程序,而用catch(…)可以捕获任意类型的异常,使得程序可以继续运行,匹配的好处是可以知道是什么类型的错误,但这个catch(…)只知道报错,不知道是什么类型的错误还是很危险的,总比没有好。
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理
让使用者知道函数会抛出什么类型的异常
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread(thread && x) noexcept;
不管是哪个派生类发生了异常,catch捕捉的时候是捕捉子类的异常
class People
{
public:
People(const string& name, const string& sex, const string& age)
:_name(name)
,_sex(sex)
,_age(age)
{}
virtual string what()const
{
string str = "PeopleException";
str += _name;
str += " ";
str += _sex;
str += " ";
str += _age;
return str;
}
private:
string _name;
string _sex;
string _age;
};
class student :public People
{
public:
student(const string&name, const string& sex, const string& age, const string&interest)
:People(name,sex,age)
,_sname(name+"stud")
,_ssex(sex + "stud")
,_sage(age + "stud")
,_sinterest(interest)
{}
virtual string what()const
{
string str = "studentException";
str += " ";
str += _sname;
str += " ";
str += _ssex;
str += " ";
str += _sage;
str += " ";
str += _sinterest;
return str;
}
private:
string _sname;
string _ssex;
string _sage;
string _sinterest;
};
void test()
{
if (rand() % 3 < 2)
{
throw student("zjt", "nan", "18", "computer");
}
else
{
cout << "success" << endl;
}
}
int main()
{
srand(time(0));
while (1)
{
Sleep(1000);
try
{
test();
}
catch (const People& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "unkonw Exception" << endl;
}
}
return 0;
}
个人理解:有点想多态,throw的是子类的,catch基类,子类切片给基类,但此时是一种多态调用。
因此可以throw子类,catch基类
优点
异常对象定义好了,相比错误码的方式可以清晰准确的展示出各种信息,便于调式
错误码是层层返回,异常是直接返回
缺点
标准库定义的不够好用,还想看堆栈等都不太支持,所以很多公司自己定义一套自己的,导致非常混乱
c++没有垃圾回收器,所以申请内存和释放内存位置要十分小心,可能会导致内存泄露,需要RAII机制进行补充
异常有时会导致程序的执行流乱跳,并且非常混乱,并且是运行时出错抛异常就会乱跳 ,这会给我们跟踪调试增加困难。
总结
异常总体而言,利大于弊鼓励使用!
double f()
{
double a, b;
cin >> a >> b;
if (b == 0)
{
throw "除0错误";
}
else
return a / b;
}
void Fun()
{
int* p1 = new int;
int* p2 = new int;
cout << f() << endl;
delete p1;
delete p2;
}
int main()
{
while (1)
{
try
{
Fun();
}
catch (const char* str)
{
cout << str << endl;
}
catch (...)
{
cout << "unknow Exception" << endl;
}
}
return 0;
}
如上所示,如果此时f()函数发生异常,那么该程序申请的内存就无法delete,就会造成内存泄露,找到一种解决措施——智能指针。
RAII是指导思想:资源获得即初始化,是一种利用对象生命周期来控制程序资源(如内存,文件句柄,网络连接等)简单技术
在对象构造时获取资源,使得对资源的访问在对象生命周期内始终有效,最后在析构时释放资源。
将资源托管给一个对象
智能指针是一种特殊的类,此时的类成员是一个指针。
template<class T>
class SmartPtr
{
public:
SmartPtr(T*ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "~SmartPtr()" << endl;
delete _ptr;
_ptr = nullptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
SmartPtr<int>ptr1(new int);
SmartPtr<int>ptr2(new int);
SmartPtr<pair<string, int>>ptr3(new pair<string, int>);
return 0;
}
可以看到此时就算有与之前一样的异常也同样不会出现内存泄露这个问题。
智能指针延申出来一个问题——浅拷贝,它所需要的是像原生指针一样的。
因为智能指针本意是为了托管资源,防止资源的内存泄露,此时分不清指向的结点时拷贝的还是原先指向的,会造成二次析构。为了解决这个问题,c++98出了一个auto_ptr
auto_ptr:管理权限转移,被拷贝的对象悬空,很多公司明确要求,不能使用auto_ptr
auto_ptr<int>ptr1(new int);
auto_ptr<int>ptr2(new int);
*ptr1 = 10;//此时没报错
ptr2 = ptr1;
//*ptr1 = 10;//此时就会报错,因为其已经悬空了
cout << *ptr2 << endl;//此时输出的是10
所以auto_ptr其实根上述实现的smart_ptr差不多,只不过拷贝构造是
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
c++11库才更新智能指针的实现,在这之前,boost(一个c++社区)搞出了更好用的scoped_ptr/shared_ptr/week_ptr,
c++11取其精华——unique_ptr/shared_ptr/weak_ptr
unique_ptr实现原理:简单粗暴的防止拷贝,同时也防止赋值运算符重载
与我们自己实现唯一不同:
方法一:
private:
Unique_Ptr(Unique_Ptr<T>& st);
Unique_Ptr&operator=(Unique_Ptr<T>& st);
这是boost中scoped_ptr的实现方式,如果不声明为私有的话,可能有人会定义拷贝构造,或者运行时报错,但我们要的是编译就报错。
unique_ptr实现如下
直接将拷贝和赋值强制性疯掉。
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
原理:通过引用计数的方式实现多个ptr之间的资源共享,每个ptr内部,都维护一份计数,用来记录该资源被几个对象共享,对象被销毁时,对象的引用计数-1,如果引用计数时0,说明自己是最后一个使用该资源的对象,必须释放,如果不是0,那么还不能释放该资源,否则其他对象变成野指针。
template<class T>
class Shared_Ptr
{
public:
void Release()
{
if (--(*_pcount) == 0 && _ptr)
{
cout << "Release()" << endl;
delete _ptr;
_ptr = nullptr;
delete _pcount;
_pcount = nullptr;
}
}
Shared_Ptr(T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
~Shared_Ptr()
{
cout << "~Shared_Ptr()" << endl;
Release();
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
Shared_Ptr(const Shared_Ptr<T>& st)
:_ptr(st._ptr)
, _pcount(st._pcount)
{
(*_pcount)++;
}
T* get() const//获得指针
{
return _ptr;
}
int use_count()//返回计数个数
{
return *_pcount;
}
Shared_Ptr& operator=(const Shared_Ptr<T>& st)
{
//if(this!=&st)
/*如果是按上述写的话,如果指向的是同一块空间还是要走一遍
代码*/
if (_ptr != st._ptr)
{
Release();
_ptr = st._ptr;
_pcount = st._pcount;
(*_pcount)++;
}
return *this;
}
private:
T* _ptr;
int* _pcount;
};
为了解决循环引用这个问题,c++引入了weak_ptr
实现原理:
weak_ptr拷贝构造shar_ptr,本质使weak_ptr指向shar_ptr,但是不参与计数管理。
通俗来讲就是,我不参与你的计数管理,但是我可以指向你的资源,资源的释放还是你自己的事情。
template<class T>
class Weak_Ptr
{
public:
Weak_Ptr()//weak_ptr是无参构造
:_ptr(nullptr)
{}
Weak_Ptr(const Shared_Ptr<T>&st )
:_ptr(st.get())
{}
Weak_Ptr<T>& operator=(const Shared_Ptr<T>& st)
{
if (_ptr != st.get())
{
_ptr = st.get();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
struct ListNode
{
/*Shared_Ptr_next = nullptr;
Shared_Ptr_prev = nullptr;*/
Weak_Ptr<ListNode>_next;
Weak_Ptr<ListNode>_prev;
int _val = 0;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
Shared_Ptr<ListNode>p1(new ListNode);
Shared_Ptr<ListNode>p2(new ListNode);
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1->_next = p2;
p2->_prev = p1;
上述中我们删除直接都是delete,但是申请方式不一定就是new出来的单个指针
给不同申请方式的对象定义特定的仿函数,使得可以特定的删除叫做定制删除器
以unique_ptr模拟实现
template<class T>
struct default_delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
template<class T, class D = default_delete<T>>
class Unique_Ptr
{
public:
Unique_Ptr(T* ptr)
:_ptr(ptr)
{}
~Unique_Ptr()
{
cout << "~Unique_Ptr()" << endl;
/*delete _ptr;
_ptr = nullptr;*/
D del;
del(_ptr);
_ptr = nullptr;
}
..........截取部分代码
此时
Unique_Ptr<Date> up1(new Date);
Unique_Ptr<Date, DeleteArray<Date>> up2(new Date[10]);
Unique_Ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date)* 10));
Unique_Ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));