可可西

C++异常深入剖析

C++异常机制由两部分组成,即抛出端处理端当程序抛出一个异常,控制权不会再回到抛出端

在抛出端,我们可以抛出一个数或对象,也可以抛出一个指针。

值得注意的是:被抛出的数、对象或指针都会产生一个副本,这个副本最后被传递给处理端。

完成抛出动作之后,原来的数、对象或指针会随之被销毁。

因此要保证此时指针所指向的内存区域不会被回收,否则,抛出的指针将指向一块无效内存区域。

另外,这个副本是对象的静态类型(不含有动态运行时特性)的拷贝构造函数创建出来的。

// 最后会在屏幕上打印出"A"
// 表明在抛出端产生对象副本时,调用的是A的拷贝构造函数
class A
{
public:
	A(){}
	A(const A& a)
	{
		printf("A\n");
	}
	~A(){}
};

class B : public A
{
public:
	B(){}
	B(const B& a)
	{
		printf("B\n");
	}
	~B(){}
};

void throwFunction()
{
	B b;
	A& a = b;
	throw a; // 此时会调用A的拷贝构造函数,而不是B的拷贝构造函数
}

int main(int argc, char* argv[])
{
	try{
		throwFunction();
	}
	catch(const A& e)
	{
		// ... ...
	}
	
	return 0;
}

++++++++++++++++++++++++++++++++++++++++++++

从以上我们得知,在throw XX时,会进行一次静态拷贝操作。

为了避免进行第二次拷贝,在处理端应使用引用接受被抛出来的对象,这个过程和函数参数传递类似。

如果程序没有处理异常,这该异常将由操作系统来接管,在Debug时,会弹出未处理的异常窗口,程序也因此中断运行。

 

需要注意的是,抛出端和处理端必须处于同一个模块内,异常才能被捕捉到。

例如:在try块中调用第三方dll的一个函数,并在其中发生了异常,那么catch块是捕捉不到该异常的。

 

关于处理端有五点值得注意:

(1) 对基本类型,不存在隐式转换

例如:throw出int类型的数时,catch(double a)捕捉块是捕捉不到该异常的。

(2) 在抛出对象时,对于多个catch块情况,应先将子类写在前,父类写在后

     因为在匹配时,是从前往后匹配的。

(3) 当某一catch被匹配上

     若在该catch块中直接throw,将导致该异常对象被重新抛出,此时不会进行拷贝。只是将该异常对象继续传递给下面的catch块

     若在该catch块中throw XX,将会对XX对象进行静态拷贝。并将拷贝后的副本继续传递给下面的catch块。

(4) catch(const void* a)可以捕捉任何指针类型的异常

(5) catch(...)可以捕捉任何异常,如果需要用到这个catch块,确保该块放在末尾

 

class A
{
public:
	A(){}
	~A(){}
};

class B : public A
{
public:
	B(){}
	~B(){}
};

void throwFunction()
{
	B b;
	throw b;
}

int main(int argc, char* argv[])
{
	try{
		throwFunction();
	}
	catch(const B& e) // 子类写在父类前面
	{
		// ... ...
	}
	catch(const A& e)
	{
		// ... ...
	}
	catch(...)
	{
		// ... ...
	}
	
	return 0;
}

============================

Unwinding:现代编译器基本都支持unwinding功能,这使得在一个异常发生时,会将堆栈中的所有对象都析构掉,防止资源泄漏。  

关于C++异常更多细节请参看:C++异常机制的实现方式和开销分析

 

posted on 2011-09-23 17:00  可可西  阅读(1043)  评论(1编辑  收藏  举报

导航