C++日积月累—异常处理

前言

异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。

try-catch

如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:

try
{
   // 保护代码
}catch( ExceptionName e1 )
{
   // catch 块
}catch( ExceptionName e2 )
{
   // catch 块
}catch( ExceptionName eN )
{
   // catch 块
}

如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。

catch语句匹配被抛出的异常对象时,如果catch语句的参数是引用型,则该参数直接引用到异常对象上;如果catch语句的参数是传值的,则拷贝构造一个新的对象作为catch语句的参数的值。语句结束时,先析构catch的参数对象,再析构throw语句抛出的异常对象。

catch语句匹配异常对象时,规则很严格,不会做隐式类型转换。除了非const到constg、派生类到基类的转换、数组和函数类型转换对应的指针类型。

再catch语句中可以使用不带表达式的throw语句将捕获的异常重新抛出,让外层catch可以再次捕获。本层其他catch不能再次捕获。

throw;

被重新抛出的的异常对象,与当前catch形参无关,比如派生类的异常对象被catch的基类形参捕获,再次抛出时,任然是派生类的异常对象。但是如果在本层catch的形参是引用类型,是可能在catch中修改异常对象的。

throw

throw是一个C++关键字,与其后的表达式构成了throw语句,语法上类似于return语句。throw语句必须被包含在try块之中,可以是被包含在调用栈的外层函数的try中。所以throw关键字的作用就是主动抛出异常。
执行throw语句时,其表达式的运算结果作为对象被复制构造为一个新的对象,放在内存的特殊位置(既不是堆、也不是栈中,Windows上是放在“线程信息块TIB”中)。这个新的对象由匹配且最接近的try-catch捕获。
由于throw语句会进行一次副本拷贝,所以异常对象支持拷贝构造。

如果catch中的形参是引用类型,则异常对象只会执行一次拷贝构造。如果形参是非引用的,有n层非引用的catch就执行n+1次拷贝构造函数。

C++异常标准

C++ 提供了一系列标准的异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:


image.png

std::exception:该异常是所有标准 C++ 异常的父类。
std::bad_alloc:该异常可以通过 new 抛出。
std::bad_cast:该异常可以通过 dynamic_cast 抛出。
std::bad_exception:这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid:该异常可以通过 typeid 抛出。
std::logic_error:理论上可以通过读取代码来检测到的异常。
std::domain_error:当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument:当使用了无效的参数时,会抛出该异常。
std::length_error:当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range:该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator
std::runtime_error:理论上不可以通过读取代码来检测到的异常。
std::overflow_error:当发生数学上溢时,会抛出该异常。
std::range_error:当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error:当发生数学下溢时,会抛出该异常。

可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:

#include <iostream>
#include <string>
#include <exception>

struct MyException : public std::exception
{
    MyException(const char* p) : log(p)
    {
        std::cout << "constructor:" << this << std::endl;
    }
    MyException(const MyException& obj) : log(obj.log)
    {
        std::cout << "copy constructor:" << this << std::endl;
    }
    ~MyException()
    {
        std::cout << "destructor" << this << std::endl;
    }
    void show_log() const throw ()
    {
        std::cout << log << this << std::endl;
    }
    const char * what() const throw ()
    {
        return log.c_str();
    }
private:
    std::string log;
};


int fun(int m, int n)
{
    if (0 == n)
    {
        throw MyException("Division by zero condition!");
    }
    return m / n;
}

int main()
{
    try
    {
        fun(1, 0);
    }
    catch (MyException e)
    {
        e.show_log();
    }
}

结果:
image.png

其中what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。

noexcept

noexcept关键字告诉编译器,函数不会发生异常,这有利于编译器对程序做更多优化。如果运行时,noexcept函数向外抛出了异常(函数内部捕获异常并完成处理不算),程序就调用std::terminate()函数,该函数内部会调用std::abort()终止程序。
建议使用noexcept的场景:

  • 移动构造函数(move constructor)
  • 移动分配函数(move assignment),就是使用move的赋值运算符重载
  • 析构函数(destructor),析构函数默认会设置noexcept
  • 叶子函数(Leaf Function)。叶子函数是指在函数内部不分配栈空间,也不调用其它函数,也不存储非易失性寄存器,也不处理异常。

前面例子的fun加上noexcept,则再执行时会直接中断,不会抛出异常

int fun(int m, int n) noexcept
{
    if (0 == n)
    {
        throw MyException("Division by zero condition!");
    }
    return m / n;
}

还可以使用有条件的noexcept,比如把上面的noexcept改成noexcept(false),就会正常抛出异常

构造函数初始化列表的异常机制

构造函数没有返回值,所以应该用异常来报告发生的问题。构造函数抛出异常就意味着该构造函数没有执行完,所以其对应的析构函数不会被自动调用,因此构造函数应该先析构所有已初始化的基对象、成员对象,再抛出异常。

myClass::myClass(type1 pa1) 
    try:  _myClass_val (初始化值)  
{  
  /*构造函数的函数体 */
}  
  catch ( exception& err )  
{ 
  /* 构造函数的异常处理部分 */
};

析构函数被期望不向函数外抛出异常。析构函数抛出异常时,将直接调用terminator()系统函数终止程序。如果一个析构函数内部抛出了异常,就应该在该析构函数内部捕获、处理了该异常,不能让异常被抛出析构函数之外。

后记

处理各种可能发生的异常,除了抛出异常,我们还可以用返回err_code的方法来处理。
选择返回error_code还是抛异常,可以参考boost asio的设计,一个函数提供两个接口,返回error_code的和抛异常的。返回error_code的适合即时解决问题,抛异常的适合自己不处理交由别人解决。逻辑复杂的可以用状态机管理,比如boost meta state machine。

使用异常和不使用异常比,二进制文件大小会有约百分之十到二十的上升,移动平台对包体大小很敏感,所以移动平台慎用!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 技术交流QQ群:1027579432,欢迎你的加入! 1.Cpp中的异常处理 异常是程序在执行期间产生的问题。C+...
    CurryCoder阅读 557评论 0 2
  • 异常的抛出 在C++中,通过throw一个表达式来引发异常,被抛出的表达式的类型以及当前的调用链共同决定了哪段处理...
    土豆吞噬者阅读 1,110评论 0 2
  • 1、C语言异常处理 1.1、异常的概念 异常:程序在运行过程中可能产生异常(是程序运行时可预料的执行分支),如:运...
    金色888阅读 571评论 0 0
  • 关于c++的异常处理,网上有很多的争议,本文会介绍c++的异常处理的使用,以及我们应该使用异常处理吗,以及使用异常...
    this_is_for_u阅读 1,083评论 0 0
  • 引用来源 概要 异常是程序执行期产生问题,比如尝试除以零的操作。异常提供了一种转移程序控制权的方式。C++ 异常处...
    googoler阅读 1,619评论 0 0