[C++] 异常

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.栈解退

栈解退,是指当有异常抛出时,从throwtry块之间的所有的自动对象,都将被析构。

编译器在处理函数的调用时,是将函数的地址放到栈中,如果函数创建了自动变量,则将自动变量也会添加到栈中,当调用结束后,依次出栈,同时释放其自动变量。当函数出现异常时,函数的执行终止,此时释放该函数对应的栈,并返回到调用它的函数,如果该函数没有处理异常程序,继续释放栈并返回,直到找到一个位于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_errorruntime_error异常及其子类位于头文件stdexcept中,这些类的构造方法接受一个string对象或常量字符串作为参数,并使用了基类exceptionwhat()方法返回字符串,如:

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值