C++高级特性:异常概念与处理机制(十四)

1、异常的基本概念
  • 异常:是指在程序运行的过程中发生的一些异常事件(如:除数为0,数组下标越界,栈溢出,访问非法内存等)

  • C++的异常机制相比C语言的异常处理:

    • 函数的返回值可以忽略,但异常不可以忽略(忽略异常程序会结束)
    • 整型返回值没有任何语义信息,而异常却包含语义信息,有时从类名中就能体现出来
    cpp 复制代码
    try{
        throw 异常值;
    } catch (异常类型1 异常值1) {
    
    } catch (异常类型2 异常值2) {
    
    } catch (异常类型3 异常值3) {
    
    } catch (...){      // 任何异常都可以捕获
    	
    }
2、异常使用
  • 抛出异常位置后面的代码将没有机会执行,并且跳转到对应的捕获函数进行捕获
  • 通过不同类型的异常捕获进行处理,对于没有适合的捕获将会走默认的任意异常捕获
  • 如果没有任意异常捕获,并且向外抛出知道main也没有处理,那么程序将会直接结束!
2.1、异常的抛出与捕获
cpp 复制代码
void test1()
{
    try {
//         throw 1;								// 
//        throw 'a';
        throw 3.14f;
    } catch (int e) {
        std::cout << "int 异常值为: " << e << std::endl;
    } catch (char e){
        std::cout << "char 异常值为: " << e << std::endl;
    } catch (...){
        std::cout << "默认异常处理" << std::endl;
    }
}
2.2、栈解旋

异常被抛出后,从进入try块起到异常抛出前(throw),这期间在栈上创建的所有对象都会被自动析构。析构的顺序与构造的顺序相反,这一过程被称为栈解旋。

cpp 复制代码
void test2()
{
    try {
        A a1(10);
        A a2(20);
        A a3(30);
        throw 1;
        A a4(40);
    } catch (int e){
        std::cout << "int 异常值为: " << e << std::endl;
    } catch (char e){
        std::cout << "char 异常值为: " << e << std::endl;
    } catch (...){
        std::cout << "默认异常处理" << std::endl;
    }
    std::cout << "------------------" << std::endl;
}
/*
A(), m = 10
A(), m = 20
A(), m = 30
~A(), m = 30
~A(), m = 20
~A(), m = 10
int 异常值为: 1
------------------
*/
3、异常的接口声明
  • 异常的接口声明描述一个函数可以抛出哪些类型的异常
  • 为什么要抛出异常:有时候当前代码中不希望看到任何的异常处理,当遇到异常时向外抛出,并集中处理
3.1、函数默认

函数声明时不指定任何异常信息的描述,表示可以抛出任何异常

cpp 复制代码
void test3()
{
//    throw 1;
//    throw 'a';
    throw "string";
}
3.2、抛出指定类型异常
  • 被指定只能抛出int和char类型的异常,抛出其他类型的异常将无法捕获
  • 但这种具体化可能抛出异常的类型写法在C++17中被移除了,C++17中推荐不写或者写noexcept(false)表示可能有异常抛出。
cpp 复制代码
void func4() throw(int, char)    // noexcept(false)
{
    throw "hello";
}
void test4()
{
    try {
        func4();
    } catch (int e){
        std::cout << "int 异常值为: " << e << std::endl;
    } catch (char e){
        std::cout << "char 异常值为: " << e << std::endl;
    } catch (const char * e){
        std::cout << "string 异常值为: " << e << std::endl;
    } catch (...){
        std::cout << "默认异常处理" << std::endl;
    }
    std::cout << "------------------" << std::endl;
}
/*
terminate called after throwing an instance of 'char const*'
*/
3.3、不抛出任何异常
  • throw()表示不抛出任何异常,但是函数体内依然可以继续抛出,然而即使抛出外部捕获也无法处理
  • C++11之后推荐使用noexcept表示不抛出任何异常
cpp 复制代码
void func5() throw()		// noexcept
{
    throw 1;
}
void test5()
{
    try {
        func5();
    } catch (int e){
        std::cout << "int 异常值为: " << e << std::endl;
    } catch (char e){
        std::cout << "char 异常值为: " << e << std::endl;
    } catch (const char * e){
        std::cout << "string 异常值为: " << e << std::endl;
    } catch (...){
        std::cout << "默认异常处理" << std::endl;
    }
    std::cout << "------------------" << std::endl;
}
/*
terminate called after throwing an instance of 'int'
*/
4、异常变量的生命周期
cpp 复制代码
class MyException{
public:
    MyException(){
        std::cout << "异常变量构造函数" << std::endl;
    }
    MyException(const MyException& myException){
        std::cout << "拷贝构造函数" << std::endl;
    }
    ~MyException(){
        std::cout << "析构函数" << std::endl;
    }
};
4.1、抛出普通变量异常

抛出普通变量异常会发生拷贝构造,可能会导致一部分的性能丢失

cpp 复制代码
void Life_test1()
{
    try{
        throw MyException{};
    } catch (MyException e){
        std::cout << "----------" << std::endl;
    }
}
/*
异常变量构造函数
拷贝构造函数
----------
析构函数
析构函数
*/
4.2、抛出指针类型异常

抛出指针类型异常需要注意释放对象的内存空间,长期不释放会导致堆区空间告急

cpp 复制代码
void Life_test2()
{
    try{
        throw new MyException();
    } catch (MyException* e){
        std::cout << "----------" << std::endl;
        delete e;
    }
}
/*
异常变量构造函数
----------
析构函数
*/
4.3、抛出引用类型异常

推荐使用抛出引用类型的异常处理,不会进行拷贝和手动分配堆区空间的问题,不会造成内存泄漏和性能负担

cpp 复制代码
void Life_test3()
{
    try{
        throw MyException();
    } catch (MyException& e){
        std::cout << "----------" << std::endl;
    }
}
/*
异常变量构造函数
----------
析构函数
*/
5、异常的多态
  • 实际开发中应该会对所有的异常进行封装处理,不然catch语句太多太多无法处理,代码狮山

  • 这种情况就可以考虑使用多态的特性了,定义基类异常,所有可能的异常都继承基类异常,而捕获时只需要捕获基类异常即可

cpp 复制代码
class BaseException{
public:
    virtual void printException(){
        std::cout << "BaseException" << std::endl;
    }
};

class NullPointerException: public BaseException{
public:
    virtual void printException(){
        std::cout << "空指针异常!" << std::endl;
    }
};

class OutOfRangeException: public BaseException{
public:
    virtual void printException(){
        std::cout << "越界访问异常!" << std::endl;
    }
};

void polymorphism_exception_test1()
{
    try {
//        throw NullPointerException();                 // 输出: 空指针异常!
        throw OutOfRangeException();                    // 输出: 越界访问异常!
    } catch (BaseException& baseException){
        baseException.printException();
    }
}
6、C++标准异常
异常名称 描述
exception 所有标准异常的父类
bad_alloc 当operator new和operator new[]请求分配内存失败时的异常
bad_exception 这是个特殊的影响,如果函数的异常抛出列表里声明了bad_exception异常,当函数内部抛出了异常列表中没有的异常,这时调用的unexpected函数中若抛出异常,不论什么类型都会被替换为bad_exception类型
bad_typeid 使用typeid操作符获取一个nullptr指针的类型,这时抛出bad_typeid异常
bad_cast 使用dynamic_cast转换引用失败的时抛出
ios_base::failure io操作过程中出现错误
logic_error 逻辑错误,可以在运行前检测的错误
runtime_error 运行时错误,仅在运行时才可以检测的错误

logic_error子类

异常名称 描述
length_error 试图生成一个超过该类型最大长度的对象是,例如vector的resize操作
domain_error 参数的阈值错误,主要用在数学函数中。例如使用一个负值调用只能操作非负数的函数
out_of_range 超出有效范围
invalid_argument 参数不合适。标准库中,当利用一个非'0'和'1'的string对象构造bitset时抛出这个异常

runtime_error子类

异常名称 描述
range_error 计算结果超出了有意义的值域范围
overflow_error 算术计算上溢出
underflow_error 算数计算下溢出
cpp 复制代码
void standard_exception_test1()
{
    try {
        throw std::out_of_range("我越界了, 哈哈哈!");
//        throw std::bad_alloc();                         // 输出:std::bad_alloc
//        throw std::bad_cast();                          // 输出:std::bad_cast
    } catch (std::exception& e){
        std::cout << e.what() << std::endl;             // 我越界了, 哈哈哈!
    }
}
6.1、继承标准异常抛出
  • 这个函数在继承重写时需要加入noexcept或者对应的宏,防止子类异常抛出前被父类提前抛出
  • const表示这个函数只能读不能改
  • 当标准异常无法满足开发需求时,可以通过继承基类异常来编写自己的异常进行抛出,这样接口就统一了。
cpp 复制代码
class NewException: public std::exception{
private:
    std::string msg;
public:
    NewException() :msg("我异常了!"){

    }
    explicit NewException(const std::string &msg) : msg(msg) {

    }

    virtual const char *what() const noexcept override {				// 需要加入noexcept
        return msg.c_str();
    }

    ~NewException() {

    }
};

void standard_exception_test2()
{
    try {
        throw NewException();
    } catch (std::exception& e){
        std::cout << e.what() << std::endl;									// 输出:我异常了!
    }
}
相关推荐
qq_433554542 分钟前
C++ 面向对象编程:递增重载
开发语言·c++·算法
易码智能10 分钟前
【EtherCATBasics】- KRTS C++示例精讲(2)
开发语言·c++·kithara·windows 实时套件·krts
ཌ斌赋ད16 分钟前
FFTW基本概念与安装使用
c++
薄荷故人_1 小时前
从零开始的C++之旅——红黑树封装map_set
c++
悲伤小伞1 小时前
C++_数据结构_详解二叉搜索树
c语言·数据结构·c++·笔记·算法
m0_675988232 小时前
Leetcode3218. 切蛋糕的最小总开销 I
c++·算法·leetcode·职场和发展
code04号5 小时前
C++练习:图论的两种遍历方式
开发语言·c++·图论
煤泥做不到的!7 小时前
挑战一个月基本掌握C++(第十一天)进阶文件,异常处理,动态内存
开发语言·c++
F-2H7 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
axxy20007 小时前
leetcode之hot100---24两两交换链表中的节点(C++)
c++·leetcode·链表