1.抛出异常
抛出一个异常,使用throw
关键字,如:
double divide(int i, int j) {
if (j == 0) {
throw "除数不能为0!";
}
return i / j;
}
当执行到一个throw
时,其后面的语句将不再执行。
2.捕获异常
如果程序中有异常抛出,则需要对异常进行捕获,通过try...catch
语句,如:
#include <iostream>
double divide(int, int);
int main()
{
using namespace std;
int i = 10;
int j = 20;
try {
double result;
result = divide(i, j);
cout << i << "/" << j << " = " << result << endl;
j = 0;
result = divide(i, j);
cout << i << "/" << j << " = " << result << endl;
}
catch (const char * ch) {
cout << ch << endl;
}
system("pause");
return 0;
}
/*
运行结果:
10/5 = 2
除数不能为0!
*/
当进入catch语句后,将通过异常类型的对象初始化catch
子句中异常声明的参数,这和函数参数类似。
3.匹配异常
经常的做法是,定义一个异常类,并当需要抛出异常时,抛出这个异常类的一个对象,并在catch语句中使用引用来接收这个异常,如:
// myexception.h
#ifndef MYEXCEPTION_H_
#define MYEXCEPTION_H_
#include <iostream>
class MyException
{
int i1, i2;
public:
MyException(int i = 0, int j = 0): i1(i),i2(j){}
void mesg();
};
void MyException::mesg() {
std::cout << i1 << "/" << i2 << " 错误,除数不能为零!" << std::endl;
}
#endif
//throw.cpp
#include <iostream>
#include "myexception.h"
double divide(int, int);
int main()
{
using namespace std;
int i = 10;
int j = 0;
try {
double result = divide(i, j);
cout << i << "/" << j << " = " << result << endl;
}
catch (MyException& me) {
me.mesg();
}
system("pause");
return 0;
}
double divide(int i, int j) {
if (j == 0) {
throw MyException(i, j);
}
return i / j;
}
但是需要注意的是,catch子句中的me引用并非直接指向MyException(i,j)
对象,而是指向这个对象的副本,这是因为,考虑到函数执行完后,局部变量将被销毁,因此throw语句将生成一个对象的副本。
而在catch子句中使用引用,是因为:基类的引用可以指向派生类的对象。假设有一组继承关联的异常类,那么在处理这些异常时,只需要一个基类引用,就可以和所有派生类异常对象匹配,而使用派生类对象的引用,则只能捕获它本身及其它的派生类的对象。
因此,对于一个有继承结构的异常类来说,在排列catch子句时,应该将捕获派生类的catch语句放在最前面,将捕获基类异常的catch语句放在最后:
class MyException {};
class MySubException1 : public MyException{}
class MySubException2 : public MyException{}
void func()
{
try{
throw MySubException1();
throw MySubException2();
} catch (MySubException1& me1){
//......
} catch (MySubException2& me2){
//......
} catch (MySubException& me){
//......
}
}
3.1.匹配所有异常
如果对可能引发的异常类型不明确,但必须处理异常,可以使用catch(...)
语句,它可以和所有类型的异常匹配:
try{
//...
}catch(...){
//......
}
3.2.重新抛出异常
可以在catch语句中重新抛出被捕获的异常:
try{
//......
} catch(Except & e){
//statement.
throw;//重新抛出该异常
}
4.栈解退
栈解退,是指当有异常抛出时,从throw
到try
块之间的所有的自动对象,都将被析构。
编译器在处理函数的调用时,是将函数的地址放到栈中,如果函数创建了自动变量,则将自动变量也会添加到栈中,当调用结束后,依次出栈,同时释放其自动变量。当函数出现异常时,函数的执行终止,此时释放该函数对应的栈,并返回到调用它的函数,如果该函数没有处理异常程序,继续释放栈并返回,直到找到一个位于try
块中的返回地址,随后控制权将交给异常处理程序。
如下示例表示一个栈解退的过程:
//unwind.h
#ifndef UNWIND_H_
#define UNWIND_H_
#include <iostream>
#include <string>
class Description
{
private:
std::string str;
public:
Description(const std::string& str);
~Description();
};
class MyException
{
private:
int i,j;
public:
MyException(int i, int j){this->i = i;this->j = j;}
void mesg();
};
#endif
//unwind.cpp
#include <iostream>
#include "unwind.h"
Description::Description(const std::string & str) {
this->str = str;
std::cout << "Description " << str << " created." << std::endl;
}
Description::~Description(){
std::cout << "Description " << str << " was destroyed." << std::endl;
}
void MyException::mesg() {
std::cout << "Error -> Invalid number:" << i << ", " << j << std::endl;
}
//mythrow.cpp
#include <iostream>
#include "unwind.h"
void divide(int,int);
int main()
{
using namespace std;
{
Description d1("desc1");
try{
Description d2("desc2");
int i = 10;
int j = 0;
divide(i,j);
} catch (MyException& e) {
e.mesg();
}
cout << "code block Done." << endl;
}
return 0;
}
void divide(int i,int j) {
Description d3("desc3");
if (j == 0)
throw MyException(i,j);
std::cout << "i / j = " << i /j << std::endl;
}
/*
编译并运行:
@ubuntu:~$ g++ unwind.h unwind.cpp mythrow.cpp -o mythrow
@ubuntu:~$ ./mythrow
Description desc1 created.
Description desc2 created.
Description desc3 created.
Description desc3 was destroyed.
Description desc2 was destroyed.
Error -> Invalid number:10, 0
code block Done.
Description desc1 was destroyed.
*/
从以上示例中可以看出,在抛出异常后,先后释放了处于try
块和throw中的自动变量desc3、desc2,最后释放了desc1.
函数返回仅仅释放处于该函数栈中的自动变量,而
throw
则将释放try~throw
之间的所有自动变量。
5.标准库异常类
C++标准库异常中,提供了许多的异常类,其中exception
类是所有异常类的基类,位于头文件exception
中:
class exception {
public:
exception () noexcept;
exception (const exception&) noexcept;
exception& operator= (const exception&) noexcept;
virtual ~exception();
virtual const char* what() const noexcept;
}
由它派生的异常类则位于不同的头文件中,下图为标准库中异常类结构:
5.1.stdexcept异常
logic_error
和runtime_error
异常及其子类位于头文件stdexcept
中,这些类的构造方法接受一个string对象或常量字符串作为参数,并使用了基类exception
的what()
方法返回字符串,如:
class runtime_error : public exception {
public:
explicit runtime_error (const string& what_arg);
explicit runtime_error (const char* what_arg);
};
logic_error
类型的异常表示逻辑错误异常,这类异常一般是编程错误,可以通过合理的编程避免。
runtime_error
类型的异常表示程序运行时出现的异常,这类异常无法避免。
如下示例中使用了以上的异常类来处理异常:
#include <iostream>
#include <stdexcept>
using namespace std;
void showName();
int main()
{
try {
showName();
} catch (out_of_range & ex) {
cout << ex.what() << endl;
}
return 0;
}
void showName() {
char name[11];
char ch;
cout << "Enter your name: " << endl;
int i = 0;
while(cin >> ch && ch != '#') {
if (i >= sizeof(name) -1)
throw out_of_range("数组已越界!!");
name[i++] = ch;
}
while(i < sizeof(name)){
name[i] = '\0';
i++;
}
cout << "Your name is: " << name << endl;
}
/*
编译并运行:
@ubuntu:~$ g++ stdexce.cpp -o stdexce
@ubuntu:~$ ./stdexce
Enter your name:
qwertyuiop#
Your name is: qwertyuiop
@ubuntu:~$ ./stdexce
Enter your name:
qwertyuiopasdfghjkl
数组已越界!!
*/
5.2.bad_alloc异常
bad_alloc
异常处于头文件new
中,当使用new
或者new []
动态分配内存失败时,将抛出该异常:
#include <iostream>
#include <new>
int main()
{
try{
int* i = new int[100000000000];
} catch (std::bad_alloc& ex) {
std::cerr << "bad_alloc caught:" << ex.what() << std::endl;
}
return 0;
}
/*
编译并运行:
@ubuntu:~$ g++ badall.cpp -o badall
@ubuntu:~$ ./badall
bad_alloc caught:std::bad_alloc
*/
5.3.bad_cast异常
bad_cast
异常位于头文件typeinfo
中,当引用类型使用dynamic_cast
进行类型转换失败时,将抛出该异常,如:
#include <iostream>
#include <typeinfo>
class Animal
{
public:
virtual ~Animal(){}
};
class Dog : public Animal{};
int main()
{
Animal a1;
try {
Dog& p1 = dynamic_cast<Dog&>(a1);
} catch (std::bad_cast& ex) {
std::cerr << "bad_cast caught: " << ex.what() << std::endl;
}
return 0;
}
/*
编译并运行:
@ubuntu:~$ g++ badcast.cpp -o badcast --std=c++11
badcast.cpp: In function ‘int main()’:
badcast.cpp:15:34: warning: dynamic_cast of ‘Animal a1’ to ‘class Dog&’ can never succeed
Dog& p1 = dynamic_cast<Dog&>(a1);
^
@ubuntu:~$ ./badcast
bad_cast caught: std::bad_cast
*/
6.自定义异常
自定义异常使,可通过继承标准库中提供的异常类来实现,如:
#include <iostream>
#include <exception>
class MyException:public std::exception
{
private:
int a,b;
public:
MyException(int a1,int b1):a(a1),b(b1){}
virtual const char* what();
};
const char * MyException::what() {
const char* msg = "Invalid argument";
return msg;
}
7. noexcept关键字
在C++11中,提供了noexcept
关键字来指定某个函数不会抛出异常,共有如下三种使用方式:
noexcept
异常说明
直接在函数参数列表后使用noexcept
关键字,表示该函数不会抛出异常:
void show() noexcept;
编译器不会在编译时检查noexcept
,如果在被noexcept
修饰的函数中使用throw
,仍将编译通过。
- 带参数的
noexcept
异常说明
noexcept
接受一个可以转换为bool值的参数,当该参数为true
时,则说明函数不会抛出异常,反之:
void show() noexcept(true);//不会抛出异常
void show() noexcept(false);//会抛出异常
noexcept
运算符
noexcept
运算符是一个一元运算符,返回值为bool类型,一般作为noexcept
的实参使用,如:
void show() noexcept(noexcept(isNull()));//isNull()返回true,则说明不会抛出异常
还有一种形式为:
noexcept(e);
表示当e调用的所有函数都做了不抛出说明、且e本身不含有throw
时,返回true
,否则false
。
8.有关异常注意事项
当动态分配内存和异常结合使用时,必须注意内存是否可以释放,比如下面示例:
void func() {
int* arr = new int[1024];
...
if (true)
throw exception();
..
delete [] arr;
}
在以上代码中,throw后的delete
语句不再执行,由于栈解退,将删除栈中的变量arr,但是其指向的内存没有释放,造成了内存泄漏。
因此,针对这种情况,可以在catch块中释放该内存,然后重新抛出:
void func() {
int* arr = new int[1024];
...
try {
if (true)
throw exception();
...
} catch (exception& ex) {
delete [] arr;
throw;//重新抛出
}
...
delete [] arr;
}