C++中的异常实例详解

目录
  • 1.异常
    • 1.1C语言中处理错误的方式
    • 1.2C语言处理错误方式的缺陷
  • 2.C++异常
    • 2.1异常相关关键字
    • 2.2异常的使用
      • 2.2.1异常的抛出和匹配原则
    • 2.3异常的重新抛出
    • 2.4自定义异常体系
    • 2.5异常安全
    • 2.6异常规范
    • 2.7C++标准库的异常体系
    • 2.8异常的优缺点
      • 2.8.1优点
      • 2.8.2缺点
  • 总结

1. 异常

异常: 异常是面向对象与法处理错误的一种方式

1.1 C语言中处理错误的方式

  • 返回错误码 (很多API接口都会把错误码放到errno当中)
  • 终止程序 (assert终止、除零错误、段错误) [产生信号去终止进程]
  • C标准库中setjump和longjump组合

1.2 C语言处理错误方式的缺陷

  • 使用错误码时,还需要查看错误码表,去找每个错误码的含义
  • 当一个函数是通过返回值去输出数据,那么发生错误的时候很难处理 (不好区设置发生错误时返回什么)
  • 如果调用的函数栈很深时,使用错误码去一层层返回时,处理起来很麻烦
//第2点
T& operator[](int index)
{
	//问题: 当访问的下标index超出容器的范围,此时该返回什么值?
    //解决方案: 可以再传一个参数,用于获取真正的结果(当输出型参数), 返回值就用于判断是否正确的返回了.
    //实际上还是很麻烦
}

2. C++异常

2.1 异常相关关键字

  • try: try块中放置可能会抛出异常的代码,这段代码被称为保护代码
  • catch: catch用于捕获异常,catch块中写处理异常的代码。它跟在try的后面,1个try后面可以跟多个catch
  • throw: throw用于抛出异常
try
{
    //保护代码(可能会抛出异常的代码)
}catch(ExceptionType e1)
{
    //处理异常
}catch(ExceptionType e2)
{
    //处理异常
}catch(ExceptionType eN)
{
    //处理异常
}

2.2 异常的使用

2.2.1 异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该匹配到哪个catch块
  2. 异常的抛出会去匹配在整个调用链中与该对象类型相匹配且距离最近的那个
  3. 当异常抛出后,执行流会直接跳转到整个调用链当中能catch到异常的位置处,不能catch的地方就不会执行了,所以这可能导致内存泄漏、文件流没关闭、锁未释放**等情况。(free、fclose、unlock未执行)
  4. catch(…)可以用来捕获任意类型对象的异常,一般用于表示"未知异常"或被用作异常的重新抛出时的接收catch块
  5. 在抛出与捕获的类型匹配上并不全都是类型相同才能匹配。我们可以使用基类去捕获 —> 抛出的派生类的对象。 //会在下面具体讲解

· 原则2:函数调用链中的异常 - 栈展开的匹配原则

  1. 查看throw是否在try的内部,如果存在能匹配到的catch语句,就跳转到catch中进行异常的处理。
  2. 如果没有匹配的catch就退出当前栈,去查找调用链当中的能够匹配到的catch
  3. 如果在main函数中都没有找到匹配的catch,则终止程序。

栈展开:上述的这个沿着调用链去逐个栈中查找匹配的catch语句的过程就是栈展开。

//代码演示:f3()中抛出异常,该异常会被f1()捕捉,而在main函数中的catch是无法捕捉到的
void f3()
{
	throw 123;
}
void f2()
{
    f3();
}
void f1()
{
    try
    {
        f2();
        cout << "f1() not catched Exception!" << endl;
    }catch(int& err)
    {
        cout << "f1()-err: " << err << endl;
    }
}

int main()
{
    try
    {
        f1();
        cout << "main not catched Exception!" << endl;
    }catch(int& err)
    {
        cout << "main-err: " << err << endl;
    }
    return 0;
}

注意:

抛出异常对象后,会生成一个异常对象的拷贝(可能是一个临时对象),这个拷贝的临时对象在被catch后会被销毁。异常的执行流执行顺序:从throw抛出异常处跳转到调用链中能够匹配的catch语句中;然后执行catch块中的代码;catch执行完毕后在当前函数栈中顺序执行。(整个调用链中没有匹配的就终止程序)

2.3 异常的重新抛出

 可能会存在单个catch不能完全处理一个异常的情况,在经过一些校正处理后,我们希望将该异常交给外层调用链中的函数来处理,此时我们可以通过在catch中重新抛出异常的方式把异常传递给调用链的上层函数处理。

//在SecondThrowException()函数中我们要delete[]动态开辟(new出来)的空间,
//如果不使用异常的重新抛出的话,就会造成内存泄漏问题 (也可以使用RAII)
void FirstThrowException()
{
    throw "First throw a exception!";
}

void SecondThrowException()
{
    int* arr = new int[10];
    try
    {
        FirstThrowException();
    }catch(...)
    {
        cout << "Delete[] arr Success!" << endl;
        delete[] arr;
        throw;
    }
}

void SoluteException()
{
    try
    {
        SecondThrowException();
    }catch(const char* err)
    {
        cout << err << endl;
    }
}

int main()
{
    SoluteException();
    return 0;
}

2.4 自定义异常体系

自定义异常体系实际上就是自己定义的一套异常管理体系,很多公司当中都会自定义自己的异常体系以便于规范的进行异常管理。它主要用到了我们在上面所说的一条规则: 我们只需要抛出派生类对象,然后捕获基类对象就可以了。这样的抛出与捕获方式非常便于异常的处理。

class MyException
{
public:
	MyException(string errmsg, int id)
		:_errmsg(errmsg),
		 _id(id)
	{}

	virtual string what() const = 0;	//必须放到public下才能让类外定义的成员访问到
protected:
	int _id;		//错误码
	string _errmsg;	//存放错误信息
	//list<StackInfo> _traceStack;	//存放调用链的信息
    //...
};

class CacheException : public MyException
{
public:
	CacheException(string errmsg, int id)
		:MyException(errmsg, id)
	{}

	virtual string what() const
	{
		return "CacheException!: " + _errmsg;
	}
};

class NetworkException : public MyException
{
public:
	NetworkException(string errmsg, int id)
		:MyException(errmsg, id)
	{}

	virtual string what() const
	{
		return "NetworkException!: " + _errmsg;
	}
};

class SqlException : public MyException
{
public:
	SqlException(string errmsg, int id)
		:MyException(errmsg, id)
	{}

	virtual string what() const
	{
		return "SqlException!: " + _errmsg;
	}
};

int main()
{
	try
	{
		//抛出任意的派生类对象
		throw SqlException("sql open failed", 10);
	}
    catch (const MyException& e)	//只需要捕获基类对象
	{
		cout << e.what() << endl;	//这里实际上完成了一个多态
	}
	catch (...)	//走到这里说明出现未知异常
	{
		cout << "Unknown Exception!" << endl;
	}

	return 0;
}

2.5 异常安全

  1. 最好不要在构造函数中抛异常,构造函数是完成对象的构造和初始化的,在里面抛异常可能会导致对象构造不完整或没有完全初始化。 (可能会造成内存泄漏)
  2. 最好不要在析构函数中抛异常,析构函数是完成资源的清理工作的,在里面抛异常可能导致资源没清完就结束了函数调用,从而导致内存泄漏、句柄未关闭等问题。
  3. 注意new、fopen、lock的使用

2.6 异常规范

异常规范的指定是为了让使用者知道函数可能抛出哪些异常,用法:

void func1() throw(); //表示该函数不会抛异常
void func2() noexcept; //等价于throw()  表示该函数不会抛异常
void func3() throw(std::bad_alloc);	//表示该函数只会抛出bad_alloc的异常
void func4() throw(int, double, string); //表示该函数会抛出int/double/string类型中的某种异常

2.7 C++标准库的异常体系

C++提供了一系列标准的异常,定义在中,下面是这些异常的组织形式。

异常 描述
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 当发生数学下溢时,会抛出该异常。
int main()
{
	try
    {
        vector<int> v(5);
        v.at(5) = 0;	//v.at(下标) = v[下标] + 抛异常
    }
    catch(const exception& e)
    {
        cout << e.what() << endl;
    }
    catch(...)
    {
		cout << "Unknown Exception!" << endl;
    }
}

2.8 异常的优缺点

2.8.1 优点

  1. 清晰的显示出错误信息
  2. 可以很好地解决返回值需要返回有效数据的函数 //如T& operator[ ] (int index)
  3. 在多层函数调用时发生错误,可以用直接在外层进行捕获异常
  4. 异常在很多第三方库中也有使用 //boost、gtest、gmock

2.8.2 缺点

  1. 异常会导致执行流跳转,这会使得调试分析程序时更加困难
  2. C++没有GC (垃圾回收机制),使用异常时可能导致资源泄露等异常安全问题。
  3. C++标准库的异常体系不实用
  4. C++的允许抛出任意类型的异常,在项目中不进行规范管理的话,会十分混乱。 //一般需要定义一套继承体系的异常规范

总结

到此这篇关于C++异常的文章就介绍到这了,更多相关C++异常内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解c++中的异常

    一.什么是异常处理 一句话:异常处理就是处理程序中的错误. 二.为什么需要异常处理,异常处理的基本思想 C++之父Bjarne Stroustrup在<The C++ Programming Language>中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关):另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现). Bjarne Stroustrup说:提供异常的基本目的

  • C++学习笔记之浅谈异常处理

    异常处理主要是针对能通过编译但是运行是在某个特定条件下会出现异常,程序崩溃,结果出错.来进行的东西 C++处理异常的机制是由3个部分组成的,即检查(try).抛出(throw)和捕捉(catch).把需要检查的语句放在try块中,throw用来当出现异常时发出一个异常信息,而catch则用来捕捉异常信息,如果捕捉到了异常信息,就处理它. try {被检查的语句} catch(异常信息类型 [变量名]) {进行异常处理的语句} 粘一个简单的异常处理的题: 如果三角形满足三角形内角和大于第三边才会有

  • 深入了解C++异常处理

    目录 基本的异常处理 怎么抛出异常 捕获和处理异常 不存在异常的描述 --- 标识性作用     删减符 ... 异常处理中的传参操作  --- 可以写一个变量进去 可以抛出自己类的对象 标准库当中的异常类 引发标准库中内存申请失败的异常 基本的异常处理 异常处理机制:暂缓问题处理,不在当前函数中处理,在他的调用者中处理(先上车,后补票) 什么是异常:任何东西都可以认为是异常,错误只是异常的一种 异常一旦被抛出,不做处理,如果引发异常,会调用默认abort函数终止程序 捕获和处理异常: thro

  • C++异常处理入门(try和catch)

    目录 捕获异常 发生异常的位置 开发程序是一项"烧脑"的工作,程序员不但要经过长期的知识学习和思维训练,还要做到一丝不苟,注意每一个细节和边界.即使这样,也不能防止程序出错. 专家指出,长期作息不规律 + 用脑过度的危害很大,可能会诱发神经衰弱.失眠等疾病.我就是受害者之一,曾被失眠困扰了好几年,不但入睡困难,还容易早醒.程序员要注意劳逸结合,多去健身房,多跑步,多打球,多陪女朋友旅游等,千万不要熬夜,以为深夜写代码效率高,这样会透支年轻的身体. 程序的错误大致可以分为三种,分别是语法

  • 一篇文章带你了解C++中的异常

    目录 异常 抛出异常基本操作 自定义的异常类 栈解旋 异常接口声明 异常变量的生命周期 异常的多态 c++的标准异常库 编写自己的异常类 总结 异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的返回值表示错误(有时候用1表示正确,0表示错误:有的时候0表示正确,1表示错误) 2,使用errno宏(可以简单理解为一个全局整形变量)去记录错误.(如果错误,就将被改变的全局整形变量返回) c++中仍然可以用上面的两种方法,但是有缺点. (1)返回值不统一,到底是1表示正确,还是0表示正确.

  • C++中的异常实例详解

    目录 1.异常 1.1C语言中处理错误的方式 1.2C语言处理错误方式的缺陷 2.C++异常 2.1异常相关关键字 2.2异常的使用 2.2.1异常的抛出和匹配原则 2.3异常的重新抛出 2.4自定义异常体系 2.5异常安全 2.6异常规范 2.7C++标准库的异常体系 2.8异常的优缺点 2.8.1优点 2.8.2缺点 总结 1. 异常 异常: 异常是面向对象与法处理错误的一种方式 1.1 C语言中处理错误的方式 返回错误码 (很多API接口都会把错误码放到errno当中) 终止程序 (ass

  • 浅谈JAVA中输入输出流实例详解

    java语言的输入输出功能是十分强大而灵活的,美中不足的是看上去输入输出的代码并不是很简洁,因为你往往需要包装许多不同的对象.在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流....本文的目的是为大家介绍JAVA中输入输出流实例详解. 流的层次结构 定义:        java将读取数据对象成为输入流,能向其写入的对象叫输出流.结构图如下: 1.输入输出: 输入/输出(Input/Output)是指对某

  • C++11智能指针中的 unique_ptr实例详解

    在前面一篇文章中,我们了解了 C++11 中引入的智能指针之一 shared_ptr 和 weak_ptr ,今天,我们来介绍一下另一种智能指针 unique_ptr . 往期文章参考: [C++11新特性] C++11 智能指针之shared_ptr [C++11新特性] C++11智能指针之weak_ptr unique_ptr介绍 unique是独特的.唯一的意思,故名思议,unique_ptr可以"独占"地拥有它所指向的对象,它提供一种严格意义上的所有权. 这一点和我们前面介绍

  • Angularjs中数据绑定的实例详解

    Angularjs中数据绑定的实例详解 这是一个最简单的angularjs的例子,关于数据绑定的,大家可以执行一下,看看效果 <html ng-app> <head> <title>angularjs-include</title> <script type="text/javascript" src="js/angular/angular.min.js"></script> </head

  • C++ 中构造函数的实例详解

    C++ 中构造函数的实例详解 c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初学者有所帮助. 1. 构造函数是干什么的 class Counter { public: // 类Counter的构造函数 // 特点:以类名作为函数名,无返回类型 Counter() { m_value = 0; } private: // 数据成员 int m_va

  • JSP Spring配置文件中传值的实例详解

    JSP Spring配置文件中传值的实例详解 通过spring提供方法,在配置文件中取传值 调用get方法  targetObject :指定调用的对象       propertyPath:指定调用那个getter方法 例1: public class Test1 { private String name = "nihao"; public String getName() { return name; } } Xml代码 <bean id="t1" cl

  • Linux 在Shell脚本中使用函数实例详解

    Linux 在Shell脚本中使用函数实例详解 Shell的函数 Shell程序也支持函数.函数能完成一特定的功能,可以重复调用这个函数. 函数格式如下: 函数名() { 函数体 } 函数调用方式: 函数名 参数列表 实例:编写一函数add求两个数的和,这两个数用位置参数传入,最后输出结果. root@ubuntu:/home/study# vi test3 #!/bin/bash add(){ a=$1; b=$2; z=`expr $a + $b`; echo "The sum is $z&

  • java 中匿名内部类的实例详解

    java 中匿名内部类的实例详解 原来的面貌: class TT extends Test{ void show() { System.out.println(s+"~~~哈哈"); System.out.println("超级女声"); } TT tt=new TT(); tt.show(); 只是说我们这里采用的是匿名的形式来处理. 重写了Test的show()方法,在重写好了以后,又调用了重写后的show()方法 实现代码: package cn.com; c

  • IOS 开发之swift中手势的实例详解

    IOS 开发之swift中手势的实例详解 手势操作主要包括如下几类 手势 属性 说明 点击 UITapGestureRecognizer numberOfTapsRequired:点击的次数:numberOfTouchesRequired:点击时有手指数量 设置属性 numberOfTapsRequired 可以实现单击,或双击的效果 滑动 UISwipeGestureRecognizer direction:滑动方向 direction 滑动方向分为上Up.下Down.左Left.右Right

  • Java中File的实例详解

    Java中File的实例详解 File 代表文件或者目录的类 构造函数 File(File parent,String child)---代表了指定父目录下的指定的子文件或者子目录 File(String pathname)---代表了指定路径对应的文件或者目录对象 重要方法 创建 createNewFile()---只能用来创建文件,并且一次只能创建一个文件,要求文件存储的目录必须真实存在 mkdir()---只能用来创建目录,不能用来创建多层目录 mkdirs()---创建多层目录 删除 d

随机推荐