1、C++中的异常处理
1.1、
catch语句块
可以抛出异常
1.1.1、
catch中获捕的异常可以
被重新抛出
1.1.2、
抛出的异常需要
外层的try-catch块来捕获
1.1.3、
catch(…)块中抛异常的方法是
throw
;也是将
所有异常
重新抛出
1.2、catch
块中
重新抛异常的意义
1.2.1、
可以被外层try-catch块捕获,并
重新解释异常的信息
。
1.2.2、
工程开发中使用这样的方式
统一异常类型
A、
.假设我们的私有库使用到了
第3方的库函数
,如func
。
B、
.但
其抛出的异常类型为
int类型
,很不友好。我们可以在私有库
使用func的地方捕获这个异常
,并在catch块中
重新解释
这个异常
并抛出
为我们
自定义的Exception类型
/************* 异常的重新解释 **********/
#include <iostream>
using namespace std;
//演示在catch块中可以抛出异常
void Demo()
{
try
{
try
{
throw 'c';
}
catch(int i)
{
cout << "Inner::catch(int i)" << endl;
throw i; //重新抛出异常
}
/*
catch(char c)
{
cout << "Inner::catch(char c)" << endl;
throw c; //重新抛出异常
}
*/
catch(...)
{
cout << "Inner:catch(...)" << endl;
throw;//抛出所有类型异常。
}
}
catch(...)
{
cout << "Outer::catch(...)" << endl;
}
}
/*
假设:当前的函数是第三方库中的函数。因此,我们无法修改源代码
函数名: void func(int i)
抛出异常的类型:int
-1 ==> 参数异常
-2 ==> 运行异常
-3 ==> 超时异常
*/
void func(int i)//第三方库函数
{
if(i<0)
{
throw -1;
}
if(i>100)
{
throw -2;
}
if(i == 11)
{
throw -3;
}
//正常运行时
cout << "Call func(int i) normal" << endl;
}
//以下是我们的私有库,当中使用到了第3方的库,这里需要将第3方库的
//异常类型统一成我们的异常信息格式
void MyFunc(int i)//私有库中重新定义捕获的异常信息。
{
try
{
func(i); //根据func抛出来的异常进行判断。
}
catch(int i)
{
switch(i)
{
case -1:
throw "Invalid Parameter";
break;
case -2:
throw "Runtime Exception";//运行时间异常
break;
case -3:
throw "Timeout Exception";
break;
}
}
}
int main()
{
//Demo();
cout << endl;
try
{
MyFunc(11);
}
catch(const char* cs)
{
cout << "Exception info: " << cs << endl;
}
return 0;
}
/*
小结:
重新抛出异常的意义:假如你们公司使用第三方的类库,而这个类库抛出的异常信息可能不便于理解,这个时候就要自定义抛出异常信息了,就要用到重新抛出的异常信息。
*/
2、自定义异常类类型
2.1、
对于
类类型异常
的匹配依旧是
至上而下严格匹配
2.2、
赋值兼容性原则
在异常匹配中
依然适用
,因此一般而言:
2.2.1、
匹配
子类异常
的catch放在
上部(子类兼容父类)
2.2.2、
匹配
父类异常
的catch放在
下部
(否则如果放上面,则子类异常由于赋值兼容会被父类捕获)。
2.3、
工程中会
定义一系列的异常类
,每个类代表可能出现的一种异常类型。
2.4、
代码复用
时可能
需要重解释不同的异常类
(如由于继承的层次关系,很可能父类的异常类在子类中会被重新解释)
2.5、
在定义catch语句块时推荐
使用
引用
作为参数
/************* 类类型的异常 *************/
#include <iostream>
#include <string>
using namespace std;
class Base
{
};
class Exception : public Base
{
private:
int m_id;
string m_desc;
public:
Exception(int id, string desc)//构造函数
{
m_id = id;
m_desc = desc;
}
int id() const //考虑const Exception对象时,函数加const,const的引用必须调用const类型的函数。
{
return m_id;
}
string description() const
{
return m_desc;
}
};
/*
假设:当前的函数是第三方库中的函数。因此,我们无法修改源代码
函数名: void func(int i)
抛出异常的类型:int
-1 ==> 参数异常
-2 ==> 运行异常
-3 ==> 超时异常
*/
void func(int i)//第三方库函数
{
if(i<0)
{
throw -1;
}
if(i>100)
{
throw -2;
}
if(i == 11)
{
throw -3;
}
//正常运行时
cout << "Call func(int i) normal" << endl;
}
//以下是我们的私有库,当中使用到了第3方的库,这里需要将第3方库的
//异常类型统一成我们的异常信息格式
void MyFunc(int i)//私有库中重新定义捕获的异常信息。
{
try
{
func(i); //根据func抛出来的异常进行判断。
}
catch(int i)
{
switch(i)
{
case -1:
throw Exception(-1, "Invalid Parameter"); //抛出的是对象。
break;
case -2:
throw Exception(-2, "Runtime Exception");
break;
case -3:
throw Exception(-3, "Timeout Exception");
break;
}
}
}
int main()
{
//const
Exception
&
q
=
Exception
(-
5
,
"sadfdsaf"
);
//表示这个引用不会修改对象内部的东西。
try
{
MyFunc(11);
}
catch(const Exception& e) //注意使用引用,防止生成临时对象,调用拷贝构造函数。引用效率高。先写着,后来再去理解为什么是异常类的引用?(可能会被复制?)不会被修改的对象的引用需要加const
{
cout << "Exception inFo: " << endl;
cout << " ID: " << e.id() << endl;
cout << " Description: " << e.description() << endl;
}
catch(const Base& e)//父类异常放下部,赋值兼容性原则。子类初始化父类对象。父类指针指向子类对象(多态,实际指向的是子类)。
{
cout << "catch(const Base& e)" << endl;
}
return 0;
}
3、C++标准库中提供的异常类族
3.1、
标准库(#include <stdexcept>)中的异常都是从
exception类
派生的
3.2、
exception类有
两个主要的分支
3.2.1、
①logic_error
:常用于程序中的可避免的
逻辑错误
3.2.2、
②runtime_error
:常用于程序中无法避免的
恶性错误
3.3、
标准库中的异常。
#ifndef _ARRAY_H_
#define _ARRAY_H_
#include <stdexcept> //引入异常类
using namespace std;
template <typename T, int N>
class Array
{
T m_array[N];
public:
int length() const; //const对象来调用
bool set(int index, T value);
bool get(int index, T& value);
T& operator[](int index);
T operator[](int index) const;
virtual ~Array(); //子类可以重写父类的函数
};
template <typename T, int N>
int Array<T, N>::length() const
{
return N;
}
template <typename T, int N>
bool Array<T, N>::set(int index, T value)
{
bool ret = (0 <= index) && (index < N);
if (ret)
{
m_array[index] = value;
}
return ret;
}
template <typename T, int N>
bool Array<T, N>::get(int index, T& value)
{
bool ret = (index>=0) && (index < N);
if(ret)
{
value = m_array[index];
}
return ret;
}
template < typename T, int N>
T& Array<T, N>::operator[](int index)
{
if((0 <= index) && (index < N))
{
return m_array[index]; //返回值为引用
}
else
{
//out_of_range是标准库中的异常类,抛出字符串对象
throw out_of_range("T& Array<T, N>::operator[](int index)");
}
}
template < typename T, int N>
T Array<T, N>::operator[](int index) const
{
if((0 <= index) && (index < N))
{
return m_array[index];
}
else
{
//out_of_range是标准库中的异常类
throw out_of_range("T Array<T, N>::operator[](int index) const");
}
}
template < typename T, int N>
Array<T, N>::~Array()
{
}
#endif
#ifndef _HEAPARRAY_H_
#define _HEAPARRAY_H_
#include <stdexcept>
using namespace std;
template
< typename T >
class HeapArray
{
private:
int m_length;
T* m_pointer;
HeapArray(int len);
HeapArray(const HeapArray<T>& obj);
bool construct();
public:
static HeapArray<T>* NewInstance(int length);
int length() const;
bool get(int index, T& value);
bool set(int index ,T value);
T& operator [] (int index);
T operator [] (int index) const;
HeapArray<T>& self();
const HeapArray<T>& self() const;
~HeapArray();
};
template
< typename T >
HeapArray<T>::HeapArray(int len)
{
m_length = len;
}
template
< typename T >
bool HeapArray<T>::construct() //申请堆数组。
{ //
m_length表示申请了m_length个T类型的数组
m_pointer = new T[m_length];
return m_pointer != NULL;
//注意逻辑
==
}
template
<typename T>
HeapArray<T>* HeapArray<T>::NewInstance(int length)
//初始化长度。
{
HeapArray<T>* ret = new HeapArray<T>(length);
if( !(ret && ret->construct()) )
//调用开始构造,返回到m_pointer
{
delete ret;
ret = 0;
}
return ret;
}
template
< typename T >
int HeapArray<T>::length() const
{
return m_length;
}
template
< typename T >
bool HeapArray<T>::get(int index, T& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
template
< typename T >
bool HeapArray<T>::set(int index, T value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
template
< typename T >
T& HeapArray<T>::operator [] (int index)
{
if( (0 <= index) && (index < length()) )
{
return m_pointer[index];
}
else
{
throw out_of_range("T& HeapArray<T>::operator [] (int index)");
}
}
template
< typename T >
T HeapArray<T>::operator [] (int index) const
{
if( (0 <= index) && (index < length()) )
{
return m_pointer[index];
}
else
{
throw out_of_range("T HeapArray<T>::operator [] (int index) const");
}
}
template
< typename T >
HeapArray<T>& HeapArray<T>::self()
{
return *this;
}
template
< typename T >
const HeapArray<T>& HeapArray<T>::self() const
{
return *this;
}
template
< typename T >
HeapArray<T>::~HeapArray()
{
delete[]m_pointer;
}
#endif
#include <iostream>
#include <string>
#include "Array.h"
#include "HeapArray.h"
using namespace std;
int main()
{
/*
Array<double, 5> ad;
for(int i=0; i<ad.length(); i++)
{
ad[i] = i * i;
}
for(int i=0; i<ad.length(); i++)
{
cout << ad[i] << endl;
}
*/
cout << endl;
HeapArray<char>* pai = HeapArray<char>::NewInstance(10);
if( pai != NULL )
{
//使用类模板时要指定泛型。
HeapArray<char>& ai = pai->self();
for(int i=0; i<ai.length(); i++)
{
ai[i] = i + 'a';
}
for(int i=0; i<ai.length(); i++)
{
cout << ai[i] << endl;//按照ASCII码表排列。
}
}
delete pai;
return 0;
}
4、C++异常机制中没有提供finally(始终会执行的语句块)块解决方案
4.1、
RAII技术
(Resource Aquisition Is Initialization,
资源获得即初始化
)
4.1.1、
基本的思路
:通过一个
局部对象
来表现资源
,于是局部对象的析构函数将会释放资源。即,将
资源封装成一个类
,将资源的初始化封装在构造函数里,释放封装在析构函数里。
要使用资源的时候,就实例化一个局部对象
。
4.1.2、
在抛出异常的时候
,由于
局部对象脱离了作用域
,
自动调用析构函数
,会保证资源被释放。
4.2、
具体做法
4.2.1、
直接使用局部变量。(局部作用域的问题)
try{
File f("xxx.ttt"); //使用局部对象
//其他操作
} //异常发生时和正常情况下,文件资源都在这里被释放catch {
//...
}
4.2.2、
将资源封装在一个类中(局部作用域的问题)
class Test{
public:
File *file;//资源被封装起来
Test() { file = new file(...); }//资源的初始化,
~Test() { delete file;} //资源的释放
};
try{
Test t; //使用局部对象
//其他操作
} //无论什么情况下,t在这里被释放,同时调用析构函数catch {
//...
}
4.2.3、使用智能指针(局部作用域的问题)
try
{
std::auto_ptr<file> pfile = new file();
//....
} //无论什么情况,智能指针都在这里被释放
catch(...)
{
//....
}
5、构造函数中抛出异常的问题
5.1、问题:构造未完成,析构函数不被调用,可能造成内存泄漏,因此,一般不建议
在构造函数中抛出异常。
/****** 构造函数中抛出异常 ******/
#include <iostream>
using namespace std;
class Test
{
private:
int* p;
public:
Test()
{
cout<< "Test()" << endl;
p= new int[5];
throw 10; //抛出异常,构造函数非正常结束
}
~Test()
{
cout <<"~Test()" << endl; delete[] p;
}
};
int main()
{
try
{
Test t; //t未完成构造就出异常,就跳到catch语句去,
//注意t的作用域为try的{},因没被执行完,所以
//还没调用析构函数就跳到catch作用域中—内存泄漏
}
catch (int e)
{
cout <<"Catch:"<<e << endl; //可能会导致对象析构函数没有被调用,对象是半成品对象。
}
return 0;
}
/*输出结果
Test()
Catch:10
*/
5.2、解决方案:
A、
在
构造函数中捕获异常、做善后处理后重新抛出异常
#include <iostream>
using namespace std;
class Test
{
private:
int* p1;
int* p2;
public:
Test()
{
int nStep = 0;
cout<< "Test()" << endl;
try
{
nStep = 1;
p1 = new int[5];
if( p1 == NULL)
{
//delete[] p1;
throw nStep; //制造异常
}
nStep = 2;
p2 = new int[10];
if( p2 != NULL )
{
//delete p2;
throw nStep; //制造异常
}
}
catch(int n)
{
//构造函数中发生异常时,析构函数不会被调用,所以
//要自己做善后处理!
if(n==1)
delete[] p1; //第1步出错时,销毁p1堆空间
if(n==2)
delete[] p2;//第2步出错时,销毁p2\p1堆空间
throw n; //继续抛出,通知调用者构造函数调用失败
}
}
~Test()
{
cout <<"~Test()" << endl; //构造函数执行成功的时候。
delete[] p1;
delete[] p2;
}
};
int main()
{
try
{
Test t; //Test的构造函数中可能发生异常
}
catch (int e)
{
cout <<"Catch:"<<e << endl;//构造函数中的第二步出了问题。
}
return 0;
}
B、方案二,二阶构造(可参考27课)
6、小结
6.1、
catch语句块中
可以抛出异常
6.2、
异常的类型
可以是自定义类类型
6.3、
赋值兼容性原则
在异常匹配中依然适用(子类在前,父类在后)
6.4、
标准库中的异常都是从
exception类派生
的。