C++ 异常处理

越行勤 311 2022-03-26

资料参考

《More Effective C+ +》 Scott Meyers 著 侯捷 译

C++ 异常处理

基本使用

相比 C来说C+ +扩展了异常处理的用法,C++的异常处理其实存在一些小坑,我这里单独将异常处理整理出来。

  1. throw 抛出异常可以为任意类型 基本类型 或者 自定义类
  2. 使用catch捕获异常,catch(type )只能捕获type 类型的异常
  3. catch 遵循最先吻合策略
  4. 异常不可以被忽略,如果异常没有被捕获,异常会沿着调用栈传递,传递到顶层还是没有捕获回导致程序终止

标准c++ 没有try finally

和函数传值类似特点,我们需要展开来说:

void func1()
{
	throw("抛出异样");//const char*
}

void func2()
{
	throw 1.0;//为double 不是float

}
void func3()
{
	throw;//抛出一个无法被捕获的异常,即使是catch(...)也不能捕捉到
}
//noexcept 标记函数不会throw异常  throw()
void func4()noexcept
{
	//虽然有的编译器可以通过编译,但是这样的异常无法被捕获
	throw(1);
}

捕获异常

	try
	{
		//func1();
		func2();
		//func3();
	}
	catch (const char*)
	{
		std::cout<<"捕获异常 const char*";
	}
	catch (float)
	{
		std::cout<<"捕获异常 float";
	}
	catch (double )
	{
		std::cout<<"捕获异常 double";
	}
	catch (...)
	{
		std::cout<<"捕获异常...";
	}

转发异常

	try
	{
		try
		{
			throw 1;
		}
		catch (int)
		{
			throw;//传递异常 也就是继续抛出int异常
		}
	}
	catch (int e)
	{
		std::cout<<"捕获异常 int "<<e;
	}

异常处理规范

这个已经被C++11 抛弃的东西, 很少使用了,C+ + 11引入了一种特殊的异常规范——noexcept,用于标记没有异常发生。

细节

区分 传递异常 与 传递参数 的区别!!,传递参数可以有三种方法

  1. by value
  2. by reference
  3. by pointer

异常也可以存在以上三种类型,但是存在很多不同,我们需要注意的点是

  1. 无法通过右值引用捕获异常
  2. 对象被throw时,总!!!是会copy一份,并且copy 总是以对象的静态类型为本
  3. 临时对象 无需const & 的形式进行捕获,引用即可
  4. 不会发生任何隐式类型转换 ,explicit ,唯2可以触发的转换是 子类对象到父类对象,throw有类型指针可以被void* 类型指针捕获
class except
{
public:
	except()= default;
	except(const except& e)
	{
		std::cout<<"发生了copy\n";
	}
	virtual  char* what()const noexcept
	{
		return "except";
	}
    ~except()
	{
		std::cout<<"~except()\n";
	}
};

class subExcept:public except
{
public:
	using except::except;//使用父类构造器
	virtual char* what()const noexcept override
	{
		return "subExcept";
	}
};

值传递的坏处 ,使用引用来捕获异常对象

参数传值和函数传值时,我们在使用值传递的时候往往会触发copy,异常捕获的时候还会copy一次

copy 两次!!!

	try
	{
		except e;
		throw e;
	}catch (except e)//这会发生两次copy
	{
		// 即使传入引用也会发生一次copy 因为 except 在try 中已经被销毁了,无法引用一个死掉的值,所以会发生一次copy
		// 也就说 throw 会发生一次copy ,value 也会发生一次copy
		//最好 使用引用接收
		//需要使用 const &吗,回想函数调用时 const,  这里是和函数传递的一个区别!
		throw; //不会发生新的copy 效率党最爱!
	}

为啥会copy两次呢?因为在 try块中 except已经被销毁,换句话说except 离开了他的作用域,我们throw并选择没有延长except的寿命,而是选择copy,而这里catch捕获的时候,又是使用值类型,那么就需要再次copy一次。

很可惜throw抛出对象是一股脑复制抛出,尽管抛出的异常不会被销毁。

	try
	{
		static except e;//即使 不会消亡还是会 copy
		throw e;
	}catch (except& e)
	{

	}

失去 子类信息

父类对象被子类对象赋值,当然会失去子类的信息。

	try
	{
		throw subExcept();//这里其实这样的话 只会发生一次copy 类似返回值优化
	}catch (except e)
	{
		std::cout<<"value 子类异常 可以被父类throw接受\n";
		//except e=subExcept(); 但是这里会导致 一个问题 失去了 派生部分的信息
		std::cout<<e.what()<<std::endl;
	}

解决办法也很简单,加入引用即可

	try
	{
		throw subExcept();
	}catch (except& e)
	{
		std::cout<<e.what()<<std::endl;
	}

补充

copy 总是以 静态类型为副本

	try
	{
		subExcept e;
		except& ee=e;
		throw ee;
	}catch (except& e)
	{
		std::cout<<e.what()<<std::endl;//e 其实静态类型时 except 而不是 subExcept
	}

throw; 仅仅转发,不会触发二次copy

	try
	{
		try
		{
			subExcept e;
			throw e;
		}
		catch (except& e)
		{
			throw ;//不会触发copy
		}
		catch (except& e)
		{
			throw e ;
		}

	}catch (except& e)
	{
		std::cout<<e.what()<<std::endl;
	}

自定义异常类

class myException:public std::exception
{
public:
	myException(const char* msg):m_msg(msg)//这里可以添加更加复杂的信息
	{}

	const char* what()const noexcept override //C++ 17 可以使用 string_view 代替 const char*
	{
		return m_msg.c_str();
	}
protected:
	std::string m_msg;
};

结语

异常处理是存在成本的,我们要适当的使用,可以使用if语句代替的就是用if代替。