[C++] 异常处理

异常是一种能够处理非正常行为的机制,也是异常产生端和异常处理端通信的一种方式。在软件开发时,通常的情况是,一方知道会发生何种异常但不知道如何处理,另一方不知道是否会发生异常,但在发生异常时它能够进行处理。所以,C++提供了异常处理机制。允许库的开发者在发生异常时抛出异常,而库的用户捕捉异常并进行合理的处理。

C++中与异常处理相关的关键字有:

throw:抛出异常。

try:测试某个程序块是否会抛出异常。

catch:对异常进行捕捉,然后处理。

因此,一般的异常处理程序的框架是:

库的开发者端:

void throw_test()
{
	if(something_is_bad) {
		throw exception;
	}
}

而库的用户端:

void catch_test()
{
	try {
		throw_test();
	} catch(exception & e) {
		// exception handler
	}
}

如上所示,在throw_test()中抛出一个exception异常,而在catch_test()中对throw_test()进行测试,如果发生了异常,就会被catch语句捕捉,并进行处理。


1 捕捉端是否要使用引用

读者看到上面捕捉异常时使用的是引用,那么,是否一定要使用应用呢?

一般传递参数的方式有三种:传递指针、传递值、传递应用。其实传递指针和传递值是类似的。

在捕捉异常时,如果采用指针传递,抛出异常后,就会脱离当前作用域,因此,就必须保证指针所指向的对象依然存在,可以有三种方式来保证:static对象、全局对象、堆上的对象。如果采用static和全局变量,这通常是不符合程序员的习惯,因为每次传递一个异常都必须声明一个static和全局变量,这当然不是良好的编程风格。如果采用堆上的对象,在抛出端进行new,那何时进行delete呢?用户必须保证在最后进行处理之后对对象进行delete,这对程序员产生了极大的负担。

在捕捉异常时,如果采用值传递,就必须产生两次拷贝,而且没有多态行为。具体的请看第2部分。

因此,在抛出对象时,应该尽量使用引用传递。采用应用传递没有上面的问题。


2 throw之后究竟发生了什么?

采用引用传递,通常在抛出端会有下面两种形式:

derived d;
throw d;

throw derived();

第一种方式是用的局部对象,第二中方式是用的临时对象。那么,离开当前作用域时,d会被析构,那么,它是如果保证d的对象能够传递到捕捉端呢?

当throw一个对象时,编译器会在堆上用抛出的对象复制构造一个临时的对象(G++的实现),因此,就算离开了当前作用域,在堆上同样有一个对象是抛出对象的副本。此时,根据捕捉端会有不同的行为:

由于传递指针在异常处理中通常是不合习惯的,因此,这里并不讨论传递指针。

如果捕捉端是以catch by value捕捉异常:

void catch_test()
{
	try {
		throw_test();
	} catch(exception e) {
		// exception handler
	}
}

就会调用拷贝构造函数对e进行构造。因此,如果以值捕捉异常,通常会有两次拷贝构造函数的调用。

如果捕捉端是以catch by reference捕捉对象:

void catch_test()
{
	try {
		throw_test();
	} catch(exception& e) {
		// exception handler
	}
}

就会将e绑定到那个堆上的临时对象。因此,引用传递只有一次拷贝构造函数的调用,而且,将一个基类的引用绑定到一个派生类的对象,该引用还可以使用多态的行为。

而如果以值来捕捉异常,抛出的是派生类的对象,捕捉时用的是基类的对象,此时,会发生对象切割,没有多态行为。(跟对象的参数传递类似)

这也验证了第1部分中最好以引用捕捉异常。


未完。。。

[C++] 异常处理,布布扣,bubuko.com

[C++] 异常处理

上一篇:java计算器 图形用户界面 简化版


下一篇:Cocos2dx 3.0 过渡篇(二十六)C++11多线程std::thread的简单使用(上)