【C++深度解析】47、C++ 中的异常处理(下)
程序员文章站
2022-05-29 17:49:05
...
1 catch 将捕获的异常抛出
- catch 中捕获的异常可以被重新解释后抛出
catch 可以捕获异常,也可以重新抛出,抛出的异常被外层的 catch 捕获。
void Demo()
{
try
{
try
{
throw 'c';
}
catch(int i)
{
cout << "Inter: catch(int i)" << endl;
throw i;
}
catch(...)
{
cout << "Inter: catch(...)" << endl;
throw;
}
}
catch(...) // 内层抛出的异常被外层catch捕获
{
cout << "other: catch(...)" << endl;
}
}
为什么要在 catch 语句块中重新抛出异常呢。三方库中很多异常类型为 int,我们希望将重新定义异常类型,将捕获的 int 异常类型进行重定义,然后重新抛出
// 47-1.cpp
#include<iostream>
using namespace std;
/*
假设: 函数func是三方库中的函数,我们无法修改源代码
函数名: 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 << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
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()
{
try
{
MyFunc(11);
}
catch(const char* cs)
{
cout << "Exception Info: " << cs << endl;
}
return 0;
}
这里我们假设函数 func 是库函数,抛出的异常为 int 类型,我们在函数 MyFunc 中捕获,重新定义异常再抛出。这样就可以捕获我们自己定义的异常了。
编译运行:
$ g++ 47-1.cpp -o 47-1
$ ./47-1
Exception Info: Timeout Exception
2 类类型的异常
- 异常类型可以是自定义类类型
- 对于类类型异常的匹配依旧是至上而下严格匹配
- 赋值兼容性原则在异常匹配中依然适用,所以
- 匹配子类异常的 catch 放在上部
- 匹配父类异常的 catch 放在下部
- 在定义 catch 语句时推荐使用引用作为参数
编程实验:类类型的异常
// 47-2.cpp
#include<iostream>
using namespace std;
class Base
{
};
class Exception : public Base
{
int m_id;
string m_desc;
public:
Exception(int id, string desc)
{
m_id = id;
m_desc = desc;
}
int id() const
{
return m_id;
}
string description() const
{
return m_desc;
}
};
/*
假设: 函数func是三方库中的函数,我们无法修改源代码
函数名: 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 << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
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()
{
try
{
MyFunc(11);
}
// 类类型异常捕获用引用,避免拷贝
// 先捕获子类异常,后捕获父类异常
catch(const Exception& e) // 先捕获子类异常
{
cout << "Exception Info: " << endl;
cout << " ID: " << e.id() << endl;
cout << " m_desc: " << e.description() << endl;
}
catch(const Base& e) // 后捕获父类异常
{
cout << "catch(const Base& e)" << endl;
}
return 0;
}
我们定义了异常类 Exception(继承 Base),MyFunc 函数 catch 语句将捕获的异常重新包装成类类型,再抛出,捕获类类型的异常时,由于赋值兼容性原则,子类对象可以转换为父类对象,所以要先捕获子类对象,再捕获父类对象。
编译运行:
$ g++ 47-2.cpp -o 47-2
$ ./47-2
Exception Info:
ID: -3
m_desc: Timeout Exception
3 标准库中的异常处理
标准库中的异常都是从 exception 类派生的,有两个重要分支
- logic_error:常用于程序中的可避免逻辑错误
- runtime_errror:常用于程序中无法避免的恶性错误
3.1 给Array数组类模板添加异常处理
在【C++深度解析】45、数组类模板中我们使用数值型模板参数定义了数组类模板,在重载下标操作符 “[]” 时可能发生越界,这里我们添加异常处理,处理越界访问。使用标准库中的 out_of_range。
// Array.h
#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成员函数,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 = (index >= 0) && (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 ( (index >= 0) && (index < N))
{
return m_array[index];
}
else // 增加越界访问,抛出异常
{
throw out_of_range("T& Arrau<T, N>::operator[] (int index)");
}
}
template<typename T, int N>
T Array<T, N>::operator[] (int index) const
{
if ( (index >= 0) && (index < N) )
{
return m_array[index];
}
else // 增加越界访问,抛出异常
{
throw out_of_range("T Array<T, N>::operator[] (int index)");
}
}
template<typename T, int N>
Array<T, N>::~Array()
{
}
#endif
// 47-3.cpp
#include<iostream>
#include"Array.h"
using namespace std;
void TestArray()
{
Array<int, 5> a;
for (int i = 0; i < 10; i++) // 这里故意越界访问
{
a[i] = i + 1;
}
for (int i = 0; i < a.length(); i++)
{
cout << a[i] << endl;
}
}
int main()
{
try
{
TestArray();
}
catch(...)
{
cout << "Exception" << endl;
}
return 0;
}
$ g++ 47-3.cpp -o 47-3
$ ./47-3
Exception
3.2 给HeapArray数组类模板添加异常处理
在【C++深度解析】12、构造函数、拷贝构造函数和析构函数中我们定义了数组类,并增加拷贝构造函数实现深拷贝。为了防止生成半成品对象,在【C++深度解析】17、二阶构造模式中用二阶构造进行改进。在【C++深度解析】45、数组类模板中用模板加以改造。
重载下表操作符 “[]” 时我们没有处理越界异常,这里添加越界异常处理,用 out_of_range。
// HeapArray.h
#ifndef _HEAPARRAY_H_
#define _HEAPARRAY_H_
#include<stdexcept>
using namespace std;
template<typename T>
class HeapArray
{
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; // 改为const成员函数,const对象和非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; // 增加一个const成员函数
~HeapArray();
};
template<typename T>
HeapArray<T>::HeapArray(int len)
{
m_length = len;
}
template<typename T>
bool HeapArray<T>::construct()
{
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()) )
{
delete ret;
ret = NULL;
}
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 = (index >= 0) && (index < m_length);
if (ret)
{
value = m_pointer[index];
}
return ret;
}
template<typename T>
bool HeapArray<T>::set(int index, T value)
{
bool ret = (index >= 0) && (index < m_length);
if (ret)
{
m_pointer[index] = value;
}
return ret;
}
template<typename T>
T& HeapArray<T>::operator[] (int index)
{
if ( (index >= 0) && (index < m_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 ( (index >=0 ) && (index < m_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
// 47-4.cpp
#include<iostream>
#include"HeapArray.h"
using namespace std;
void TestHeapArray()
{
HeapArray<double>* pa = HeapArray<double>::NewInstance(5);
if (pa)
{
HeapArray<double>& array = pa->self();
for (int i = 0; i < 10; i++) // 故意越界访问
{
array[i] = i;
}
for (int i = 0; i < array.length(); i++)
{
cout << array[i] << endl;
}
}
delete pa;
}
int main()
{
try
{
TestHeapArray();
}
catch(...)
{
cout << "Exception" << endl;
}
return 0;
}
$ g++ 47-4.cpp -o 47-4
$ ./47-4
Exception
4 小结
1、catch 可以将捕获的异常抛出
2、异常类型可以是自定义的类类型
3、由于赋值兼容原则,先匹配子类异常,再匹配父类异常