资料参考
《More Effective C+ +》 Scott Meyers 著 侯捷 译
C++ 异常处理
基本使用
相比 C来说C+ +扩展了异常处理的用法,C++的异常处理其实存在一些小坑,我这里单独将异常处理整理出来。
- throw 抛出异常可以为任意类型 基本类型 或者 自定义类
- 使用catch捕获异常,catch(type )只能捕获type 类型的异常
- catch 遵循最先吻合策略
- 异常不可以被忽略,如果异常没有被捕获,异常会沿着调用栈传递,传递到顶层还是没有捕获回导致程序终止
标准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,用于标记没有异常发生。
细节
区分 传递异常 与 传递参数 的区别!!,传递参数可以有三种方法
- by value
- by reference
- by pointer
异常也可以存在以上三种类型,但是存在很多不同,我们需要注意的点是
- 无法通过右值引用捕获异常
- 对象被throw时,总!!!是会copy一份,并且copy 总是以对象的静态类型为本
- 临时对象 无需const & 的形式进行捕获,引用即可
- 不会发生任何隐式类型转换 ,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代替。