首页 > 编程语言 >学懂C++(七): C++错误处理机制 -- 异常

学懂C++(七): C++错误处理机制 -- 异常

时间:2024-08-07 11:29:05浏览次数:10  
标签:抛出 C++ try 学懂 catch 错误处理 异常 throw

目录

前言

一、C 语言传统的处理错误的方式

二、C++ 异常的概念

三、异常的使用

3.1 异常的抛出和匹配原则

3.2 在函数调用链中异常栈展开匹配原则

3.3 异常的重新抛出

3.4 异常规范

四、自定义异常体系

五、异常的优缺点

优点

缺点

结论


前言

        C++ 提供了一种优雅的错误处理机制——异常(Exceptions),这是 C++11 以来的重要特性之一。异常处理使得程序员能够有效地管理运行时错误,集中处理异常逻辑,从而提高代码的可读性和可维护性。本文将详细介绍 C++ 的异常处理机制,包括概念、使用、原则、自定义异常体系,以及它的优缺点。

一、C 语言传统的处理错误的方式

C 语言的错误处理相对简单,主要有以下几种方式:

  1. 终止程序:通过 assert 等函数直接终止程序。这种方式的缺陷在于用户体验较差,尤其是对于非致命错误(如 I/O 错误)时,程序强制终止给用户带来困扰。

  2. 返回错误码:许多 C 库通过返回错误码(如 errno)来指示操作是否成功。这种方式要求程序员在每次函数调用后都手动检查错误码,容易导致错误被忽略。

以下是一个示例,展示了通过返回错误码的方式处理错误:

#include <iostream>
#include <cstdio>
#include <cerrno>

int main() {
    FILE* fout = fopen("file.txt", "r");
    
    if (!fout) {
        std::cout << "Error opening file: " << errno << std::endl;
        perror("fopen fail");
    }

    return 0;
}

二、C++ 异常的概念

C++ 的异常处理通过 throwcatchtry 关键字实现。异常提供了一种机制,当函数遇到它无法处理的错误时,可以抛出异常,让调用者处理。

  • throw:用于抛出异常的关键字。
  • catch:用于捕获异常并进行处理。
  • try:标记可能抛出异常的代码块,其后通常跟随一个或多个 catch 块。
    try {
        // 可能抛出异常的代码
    } catch (const std::exception& e) {
        // 处理异常
    }
    

三、异常的使用

3.1 异常的抛出和匹配原则

  • 类型匹配:抛出的异常类型和捕获的类型必须严格一致。否则,捕获将失败,程序将继续寻找其他匹配的 catch 块。

    try {
        throw "Error occurred"; // 抛出字符串
    } catch (int e) { // 类型不匹配
        // 不会被执行
    } catch (const char* e) {
        std::cout << e << std::endl; // 会被执行
    }
    
  • 就近原则:调用链中最接近 throw 语句的 catch 块会被优先匹配。

    void Func() {
        try {
            throw "Error"; // 抛出异常
        } catch (const char* e) {
            std::cout << e << std::endl; // 最近的catch
        }
    }
    
  • 通配符捕获:使用 catch(...) 可以捕获任何类型的异常,通常作为最后的手段。

    try {
        throw 10; // 抛出整型异常
    } catch (...) { // 捕获所有异常
        std::cout << "Unknown exception" << std::endl;
    }
    
  • 拷贝对象:抛出的异常对象会自动生成拷贝,原对象的生命周期结束后,拷贝仍然存在于 catch 块中。

  • 基类捕获子类:可以通过基类捕获派生类对象,这在实际应用中非常方便且常见。

    class BaseException {};
    class DerivedException : public BaseException {};
    
    try {
        throw DerivedException(); // 抛出派生类异常
    } catch (BaseException& e) { // 捕获基类异常
        std::cout << "Caught a base exception!" << std::endl;
    }
    

3.2 在函数调用链中异常栈展开匹配原则

  1. throw 开始查找:检查 throw 是否在 try 块内,若是则查找对应的 catch

  2. 沿调用链查找:若没有匹配的 catch,则退回到调用当前函数的函数中继续查找。

  3. 终止程序:若到达 main 函数仍未找到匹配的 catch,程序将终止,并可能触发终止处理程序。

  4. 继续执行:找到匹配的 catch 后,处理完异常后会继续执行 catch 块后面的代码。

3.3 异常的重新抛出

在异常处理后,可能需要重新抛出异常以便上层处理。这通常用于清理资源的操作,有效避免内存泄漏。

void Func() {
    int* array = new int[10]; // 动态分配内存
    try {
        // 可能抛出异常的代码
    } catch (...) {
        // 处理异常
        delete[] array; // 释放资源
        throw; // 重新抛出异常
    }
}

 

3.4 异常规范

  • 异常说明:函数后面可以使用 throw 指定可能抛出的异常类型,这在函数文档中非常有用。

  • 不抛异常:通过 throw() 声明,表示函数不抛出任何异常。

  • C++11 noexcept 关键字:表示该函数不会抛出异常,使用时可以提高性能。

    void func() throw(A, B); // 可能抛出 A 或 B
    void func() noexcept; // 不抛出异常
    

四、自定义异常体系

在大型项目中,通常会建立一套自定义的异常体系以规范异常管理。通过继承异常基类,可以方便地捕获和处理异常,增强代码的可读性与可维护性。

class MyException : public std::exception {
public:
    const char* what() const noexcept override {
        return "My custom exception occurred";
    }
};

使用自定义异常类可以提供更丰富的错误信息,并且有助于明确区分不同类型的错误。

五、异常的优缺点

优点

  1. 清晰的错误处理:异常对象可以包含丰富的错误信息,帮助定位和解决问题。

  2. 集中处理:通过 try-catch 块,可以在一个位置集中处理所有相关的错误,而不需要在每个函数中检查返回值。

  3. 资源安全:通过 RAII(资源获取即初始化)结合异常处理,可以有效避免资源泄漏和内存管理问题。

缺点

  1. 控制流复杂:异常会导致程序的执行流跳转,这可能使得代码逻辑难以跟踪,调试变得困难。

  2. 性能开销:异常处理可能带来一定的性能开销,尤其是在异常频繁发生时。

  3. 资源管理问题:C++ 不会自动回收资源,必须使用 RAII 原则以避免内存泄漏等问题,增加了学习成本。

结论

        C++ 的异常处理机制为程序员提供了一种灵活、强大的方式来处理错误。合理利用异常处理,可以显著提高代码的可维护性和可靠性。尽管使用不当可能引入复杂性,但通过良好的设计和实践,可以有效管理异常,确保程序的稳定性。在实际开发中,建议结合自定义异常体系和 RAII 原则,以确保资源的正确管理和错误的及时处理。通过掌握 C++ 的异常处理,你将能够编写出更加健壮的代码,提升软件的质量。

标签:抛出,C++,try,学懂,catch,错误处理,异常,throw
From: https://blog.csdn.net/martian665/article/details/140985945

相关文章

  • 【C++从小白到大牛】多态那些事儿(上)
    目录一、多态的概念1.1概念:二、多态的定义及实现 2.1多态的构成条件在继承中要多态还要两个条件 2.2虚函数2.3虚函数的重写2.4虚函数重写的两个例外:1.协变(基类与派生类虚函数返回值类型不同)(了解不重要)2.析构函数的重写(基类与派生类析构函数的名字不同)2......
  • C++入门基础1
    目录1.c++发展历史2.C++在⼯作领域中的应⽤3.C++学习建议和书籍推荐3.1学习难度3.2书籍的推荐4.c++第一个程序5.命名空间5.1namesapce的价值5.2namespace的定义5.2.1 正常的命名空间定义5.3命名空间的使用5.3.1指定命名空间访问5.3.2using将命名空间中某......
  • C++笔记,类和对象(上)
    对于类的初步认识目录对于类的初步认识(1)类的定义(2)类的访问限定符及封装(3)类的作用域(4)类的实例化(5)类的对象大小的计算(6)类成员函数的this指针(1)类的定义classclassName{//类体,由成员函数和成员变量组成};//一定要注意后面的分号类体中内容称为类的成员:类......
  • Qt/C++最新地图组件发布/历时半年重构/同时支持各种地图内核/包括百度高德腾讯天地图
    一、前言说明最近花了半年时间,专门重构了整个地图组件,之前写的比较粗糙,有点为了完成功能而做的,没有考虑太多拓展性和易用性。这套地图自检这几年大量的实际项目和用户使用下来,反馈了不少很好的建议和意见,经过这几年的整理,刚好趁着近期经济下行严重,抽出时间把整个地图组件重构一下......
  • C++ 学习预备知识
    1C++简介 1.1起源    C++与C语言一样,也是在贝尔实验室诞生的,名称C++来自C语言中的递增运算符++,该运算符将变量加1。这也表明,C++是C语言的扩充版本。    C++融合了3种不同的编程方式:C语言代表的过程性语言、C++在C语言基础上添加的类代表的面向对象语言、C+......
  • C++解析ini文件
    目录一.什么是ini文件二.ini文件的格式一般是什么样的1.节2.参数3.注释三.C++实现ini文件的解析四.其他这篇文章简单讨论一下ini文件。一.什么是ini文件ini文件其实就是一种配置文件,常见于Windows的系统配置文件,当然也可以是其他用途,你的用法你说了算。二.ini文件......
  • 【C++/STL】map和set的封装(红黑树)
     ......
  • C++学习笔记----Strings与String View(4)-- 字符串操作
        今天讲点简单易懂的,字符串操作,当然了,不是全部,列出几个典型的字符串操作,完整地可以参考相关资料,网上一搜一把哦。substr(pos,len):返回特定位置pos,特定长度的子字符串。find(str):返回字符串的位置,如未找到则返回string::npos。replace(pos,len,str):用新的字符串str......
  • C++学习笔记----Strings与String View(5)-- 字符串文本
    今天我们继续来学习C++的string:1、字符串文本    字符串文本通常会被解析成字符指针常量(constchar*)或者字符数组常量(constchar[]),如果想要达到声名std::string常量的目的,则需要在声明时在常量字符串后加一个s,示例如下: autostring1{"HelloWorld"}; //string1为......
  • 「队列」实现FIFO队列(先进先出队列|queue)的功能 / 手撕数据结构(C++)
    概述队列,是一种基本的数据结构,也是一种数据适配器。它在底层上以链表方法实现。队列的显著特点是他的添加元素与删除元素操作:先加入的元素总是被先弹出。一个队列应该应该是这样的:--------------QUEUE-------------———————————......