C++异常处理:掌握高效健壮代码的秘密武器

Posted 泡沫o0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++异常处理:掌握高效健壮代码的秘密武器相关的知识,希望对你有一定的参考价值。

C++异常处理全面解析:底层原理、编译器技巧与实用场景

C++异常机制:让我们迈向更安全、更可靠的代码

在编程的世界里,错误是不可避免的。无论是因为程序员的失误、不可预测的输入,还是其他外部因素,错误总是无处不在。当然,我们可以尽量通过谨慎的编程和严格的测试来减少错误,但它们依然会出现。为了应对这种情况,C++提供了一种强大的错误处理机制,那就是异常处理。通过有效地使用异常处理,我们可以编写出更健壮、更可靠的代码,使我们的程序能够更好地应对各种错误状况。

在本篇博客中,我们将详细探讨C++的异常处理机制。我们将从基本概念开始,逐步深入到try、catch、throw的使用,以及如何创建自定义异常类。接下来,我们将探讨异常处理的最佳实践,了解如何通过异常处理编写可维护的代码。最后,我们将讨论异常处理与性能之间的关系,以及如何在保证代码健壮性的同时,实现性能的最优化。

希望在阅读本文后,您将对C++异常处理机制有更深入的了解,从而编写出更加健壮、高效的代码。


C++异常处理:掌握基本概念

在深入研究异常处理之前,我们需要先了解一些基本概念。

什么是异常?

异常是程序运行过程中出现的意外情况,它可能会导致程序无法正常执行。例如,当程序试图访问一个不存在的数组元素,或者分配内存失败时,就会出现异常。为了处理这些异常,我们需要在程序中添加特殊的代码,来捕获异常并采取适当的措施,以确保程序的稳定运行。

异常处理的重要性

异常处理在编程中起着至关重要的作用。通过使用异常处理,我们可以:

  • 提高程序的健壮性:异常处理可以帮助我们识别程序中的错误,并在发生异常时采取适当的措施,从而避免程序崩溃或数据丢失。
  • 提高代码的可读性:通过将错误处理代码与正常逻辑代码分离,我们可以使程序结构更加清晰,便于阅读和维护。
  • 便于调试和定位问题:当异常发生时,异常处理机制可以提供详细的错误信息,帮助我们快速定位和解决问题。

C++异常处理的组成部分:try、catch、throw

C++的异常处理机制主要包括三个关键词:try、catch、throw。它们分别用于定义可能发生异常的代码块、捕获异常并处理,以及抛出异常。我们将在下一节中详细讨论它们的作用和用法。


探索C++异常处理的核心:try、catch、throw详解

要掌握C++的异常处理机制,首先需要了解try、catch、throw这三个关键词的用法。在这一节中,我们将逐一介绍它们的功能和使用方法。

try块的用途和使用方法

try块用于定义可能发生异常的代码段。当程序执行try块中的代码时,如果发生异常,程序将跳出try块,并开始查找与之匹配的catch块。如果没有异常发生,程序将正常执行try块中的代码,并跳过与之相关的catch块。

使用try块的基本语法如下:

try 
    // 可能发生异常的代码

catch (异常类型1 参数1) 
    // 处理异常类型1的代码

catch (异常类型2 参数2) 
    // 处理异常类型2的代码

// 更多的catch块...

catch块的作用和匹配原则

catch块用于捕获并处理异常。当程序执行到try块中的代码时,如果发生异常,程序将跳出try块,并开始查找与之匹配的catch块。匹配的原则是异常对象的类型必须与catch块中声明的异常类型相同,或者是其派生类。当找到匹配的catch块时,程序将执行该catch块中的代码,以处理异常。如果没有找到匹配的catch块,程序将终止并调用std::terminate()函数。

catch块的基本语法如下:

catch (异常类型 参数) 
    // 处理异常的代码


注意,catch块的顺序很重要。程序会按照catch块的顺序进行匹配。因此,建议首先捕获较具体的异常类型,然后再捕获较一般的异常类型。

throw语句的使用和注意事项

throw语句用于抛出异常。当程序执行到throw语句时,程序将立即终止当前函数的执行,并跳转到最近的try块。然后,程序开始查找与抛出的异常类型匹配的catch块。如果没有找到匹配的catch块,程序将继续在调用栈中向上查找,直到找到匹配的catch块或程序终止。

throw语句的基本语法如下:

throw 异常对象;

使用throw语句时,有以下几点需要注意:

  • 抛出的异常对象可以是任何类型,但通常应该使用C++标准库中定义的异常类型(如std::runtime_error)或自定义的异常类。
  • 当抛出异常时,最好使用值传递而不是引用传递,以避免引用无效对象的问题。
  • 如果在函数中抛出异常,应确保函数的资源已正确释放,以避免内存泄漏等问题。

通过掌握try、catch、throw的用法,我们已经可以编写基本的异常处理代码了。然而,在实际编程中,我们可能需要创建自定义的异常类来更好地表示和处理特定的错误情况。在下一节中,我们将介绍如何创建和使用自定义异常类。


打造你的个性化异常处理:如何创建自定义异常类

在实际编程中,我们可能会遇到一些特定的错误情况,这时候使用C++标准库提供的异常类型可能无法满足我们的需求。为了更好地表示和处理这些错误,我们可以创建自定义的异常类。在这一节中,我们将介绍如何创建自定义异常类,以及如何抛出和捕获自定义异常。

继承自std::exception的异常类

自定义异常类通常应该继承自C++标准库中的std::exception类。std::exception是一个通用的异常基类,它提供了一个名为what()的虚函数,用于获取异常的详细信息。通过继承std::exception,我们可以确保自定义异常类与C++标准库的异常类具有相同的接口。

以下是一个简单的自定义异常类的示例:

#include <exception>
#include <string>

class MyException : public std::exception 
public:
    MyException(const std::string& message) : message_(message) 

    const char* what() const noexcept override 
        return message_.c_str();
    

private:
    std::string message_;
;

在这个示例中,我们创建了一个名为MyException的自定义异常类,它继承自std::exception。MyException类包含一个私有成员变量message_,用于存储异常的详细信息。我们还重写了what()函数,以返回message_的内容。

自定义异常类的构造与析构

自定义异常类的构造函数通常需要接收一个用于描述异常的字符串参数。这个参数可以用来初始化异常类的私有成员变量,以便在what()函数中返回。此外,我们还可以为自定义异常类提供一个默认构造函数,以便在不提供详细信息的情况下创建异常对象。

自定义异常类的析构函数通常应该声明为虚函数,并使用noexcept关键字标记,以确保在异常处理过程中不会抛出新的异常。这是因为在C++中,析构函数中抛出异常可能导致未定义行为。

如何抛出和捕获自定义异常

抛出和捕获自定义异常的过程与标准异常类相同。我们可以使用throw语句抛出自定义异常对象,并在catch块中捕获并处理它。以下是一个简单的示例:

#include <iostream>
#include "MyException.h"

void foo() 
    throw MyException("Something went wrong in foo()");


int main() 
    try 
        foo();
     catch (const MyException& e) 
        std::cerr << "Caught MyException: " << e.what() << std::endl;
     catch (...) 
        std::cerr << "Caught an unknown exception" << std::endl;
    

    return 0;


在这个示例中,我们首先包含了MyException.h头文件,然后定义了一个名为foo的函数,在该函数中抛出一个MyException对象。在main函数中,我们使用try-catch语句调用了foo函数。当foo函数抛出异常时,程序将跳到与之匹配的catch块,打印出捕获到的异常信息。如果foo函数抛出了未知类型的异常,我们可以使用catch(…)语句捕获并处理它。

通过创建自定义异常类,我们可以更好地表示和处理特定的错误情况。然而,为了确保异常处理代码的质量,我们还需要遵循一些最佳实践。在下一节中,我们将介绍C++异常处理的最佳实践,以帮助您编写出可维护的代码。


C++异常处理的高级技巧:如何编写可维护的代码

在本节中,我们将探讨一些C++异常处理的最佳实践,这些实践可以帮助您编写出更加可维护、可靠的代码。

使用RAII确保资源安全

在C++中,资源分配即初始化(RAII,Resource Acquisition Is Initialization)是一种常用的编程技巧,用于确保资源在异常情况下被正确释放。RAII的基本思想是将资源的生命周期与对象的生命周期绑定,通过对象的构造和析构函数来分配和释放资源。当异常发生时,已经构造的对象会自动调用其析构函数,从而确保资源被正确释放。

为了确保资源安全,您应该:

  • 尽量使用智能指针(如std::unique_ptr和std::shared_ptr)来管理动态分配的内存。
  • 使用C++标准库中的容器和类(如std::vector和std::fstream),它们已经实现了RAII。
  • 在自定义类中实现RAII,以确保资源在构造函数和析构函数中被正确分配和释放。

避免在构造函数和析构函数中抛出异常

在构造函数中抛出异常可能导致对象处于无效状态。如果在构造函数中抛出异常,应确保已分配的资源被正确释放,以避免内存泄漏等问题。此外,避免在析构函数中抛出异常,因为在异常处理过程中,析构函数抛出的异常可能导致未定义行为。

利用异常规格说明来提高代码可读性

在C++11及更高版本中,我们可以使用noexcept关键字来指定函数不会抛出异常。这可以帮助编译器生成更高效的代码,并提高代码的可读性。当您确定函数不会抛出异常时,可以考虑使用noexcept关键字。

例如:

void myFunction() noexcept 
    // 不会抛出异常的代码


尽量避免异常规格泛化

在C++中,异常规格泛化(Exception Specification)是一种旧的异常处理机制,用于指定函数可能抛出的异常类型。这种机制通过在函数声明后添加throw关键字来实现。然而,异常规格泛化在实践中往往导致问题,如代码膨胀和运行时开销,因此不推荐使用。在C++11及更高版本中,建议使用noexcept关键字来代替异常规格泛化。

使用异常安全的设计模式

在设计和实现软件时,应尽量使用异常安全的设计模式。异常安全的设计模式是指在异常发生时能保持程序的正确性和稳定性。以下是一些异常安全的设计模式:

  • 基本异常安全(Basic Exception Safety):确保异常发生时,程序的资源不会泄漏,并能保持一致性。
  • 强异常安全(Strong Exception Safety):确保异常发生时,程序的状态不会发生改变。
  • 不抛异常安全(Nothrow Exception Safety):确保函数不会抛出异常。

在实践中,我们应该根据需求和性能考虑选择合适的异常安全设计模式。

通过遵循这些最佳实践,您可以编写出更加可维护、可靠的异常处理代码。在实际开发中,您还可以结合项目需求和团队风格,制定出更适合您的异常处理规范和技巧。


Linux环境下C++异常机制的底层原理:深入探索异常处理的内部工作

我们将深入了解Linux环境下C++异常机制的底层原理,包括编译器原理、用户态与内核态相关知识点以及异常处理与底层信号的关系。

编译器原理

C++编译器在编译时会将异常处理相关的代码转换为底层的实现,大多数情况下,这些实现依赖于Linux环境下的一些特定机制。C++异常处理的底层实现通常涉及以下几个方面:

  • 异常表(Exception Table):编译器在生成可执行文件时,会为每个函数创建一个异常表。异常表中包含了函数中可能抛出异常的位置以及与之关联的异常处理器(catch块)的信息。当异常发生时,运行时系统会查找异常表以确定如何处理异常。

  • 调用栈展开(Stack Unwinding):当异常被抛出时,运行时系统需要展开(unwind)调用栈,以便在调用栈中查找合适的异常处理器。这个过程通常涉及调用析构函数来释放栈上的资源,以保持程序的一致性。

  • 异常对象管理:当异常被抛出时,编译器需要在堆上分配异常对象。运行时系统会确保异常对象的生命周期与异常处理过程相匹配,并在异常处理结束后释放异常对象。

用户态与内核态

在Linux环境下,程序的执行可以分为用户态(User Mode)和内核态(Kernel Mode)。用户态是程序正常执行的状态,而内核态是操作系统内核执行系统调用和处理硬件中断时的状态。在C++异常处理过程中,绝大部分操作都在用户态完成,例如调用栈展开、异常对象管理等。这意味着C++异常处理机制通常不会导致内核态切换,从而减少了性能开销。

异常处理与底层信号的关系

Linux操作系统使用信号(Signal)机制来处理异常和中断。信号是一种异步事件通知机制,用于通知进程某个事件发生,如除零错误、段错误等。信号分为两类:同步信号和异步信号。同步信号是由当前执行的指令引发的,而异步信号是由其他进程或事件引发的。

C++异常处理与底层信号之间存在一定的关联。当程序发生硬件异常(如除零错误、段错误等)时,操作系统会向进程发送相应的信号。程序可以为这些信号设置信号处理函数,以便在信号发生时执行特定的操作。然而,C++异常处理与信号处理之间有一个重要区别:信号处理通常是异步的,而C++异常处理是同步的。

C++标准库提供了一种将信号处理与异常处理相结合的方法:std::signal函数。
std::signal函数允许您为特定信号设置处理函数,这些处理函数可以抛出C++异常。当信号处理函数抛出异常时,运行时系统会展开调用栈,寻找适当的异常处理器(catch块)。这样,您可以使用C++异常处理机制来处理底层信号,实现更统一的错误处理策略。

然而,将信号处理与C++异常处理相结合可能带来一些挑战,如线程安全性、资源管理等。在实践中,您需要仔细评估使用C++异常处理与信号处理的权衡,以找到最适合您项目的解决方案。

性能考虑

C++异常处理机制在设计时考虑了性能。当没有异常发生时,异常处理的开销通常非常小。然而,当异常发生时,运行时系统需要执行一系列操作(如调用栈展开、异常对象管理等),这可能导致较大的性能开销。因此,在编写高性能代码时,您应该尽量避免过度依赖异常处理,将异常处理保留给真正的异常情况。

使用异常处理的安全策略

在某些安全敏感的场景下(如系统编程、嵌入式编程等),您需要特别注意异常处理的安全性。在这些场景下,您应该:

  • 尽量避免在关键代码路径上使用异常处理,以减少潜在的安全风险。
  • 在处理底层信号时,使用信号安全的函数,并确保信号处理函数不会引发未定义行为。
  • 使用RAII和异常安全的设计模式,以确保资源在异常情况下被正确释放,并防止内存泄漏等问题。

C++异常处理的各种使用场景:灵活运用异常处理机制

在本章节中,我们将探讨C++异常处理在各种使用场景中的应用,以帮助您更好地理解如何在实际编程中灵活运用异常处理机制。

文件和网络I/O操作

在进行文件和网络I/O操作时,可能会遇到各种错误,如文件不存在、磁盘空间不足、网络连接断开等。使用C++异常处理机制可以更好地处理这些错误。例如,当文件操作失败时,您可以抛出一个自定义的FileIOException异常,并在catch块中处理错误。这样,您可以将错误处理逻辑与正常执行路径分离,使代码更易于阅读和维护。

#include <iostream>
#include <fstream>
#include "FileIOException.h"

void processFile(const std::string &filename) 
    std::ifstream file(filename);

    if (!file.is_open()) 
        throw FileIOException("Unable to open file: " + filename);
    

    // Process the file contents


int main() 
    try 
        processFile("nonexistent_file.txt");
     catch (const FileIOException &e) 
        std::cerr << "Error: " << e.what() << std::endl;
    

    return 0;


内存分配失败

当动态分配内存失败时,C++标准库会抛出std::bad_alloc异常。您可以捕获此异常,以处理内存分配失败的情况。

#include <iostream>
#include <new>

int main() 
    try 
        int *arr = new int[100000000000ULL];
     catch (const std::bad_alloc &e) 
        std::cerr << "Error: " << e.what() << std::endl;
    

    return 0;


类型转换错误

在进行类型转换时,可能会遇到错误,如字符串转换为整数时输入的字符串不是有效的整数。您可以使用C++异常处理机制来处理这些错误。例如,当字符串转换为整数失败时,您可以抛出一个自定义的InvalidConversionException异常。

#include <iostream>
#include <string>
#include "InvalidConversionException.h"

int stringToInt(const std::string &str) 
    int result;

    try 
        result = std::stoi(str);
     catch (const std::invalid_argument &) 
        throw InvalidConversionException("Invalid conversion from string to int: " + str);
     catch (const std::out_of_range &) 
        throw InvalidConversionException("Out of range conversion from string to int: " + str);
    

    return result;


int main() 
    try 
        int number = stringToInt("not_a_number");
     catch (const InvalidConversionException &e) 
        std::cerr << "Error: " << e.what() << std::endl;
    

    return 0;


无效的函数参数

在调用函数时,如果传递了无效的参数,您可以使用异常处理来报告错误。例如,当向一个divide函数传递0作为除数时,您可以抛出一个自定义的DivideByZeroException异常。

#include <iostream>
#include "DivideByZeroException.h"

double divide(double numerator, double denominator) 
    if (denominator == 0) 
        throw DivideByZeroException("Attempted division by zero");
    

    return numerator / denominator;


int main() 
    try 
        double result = divide(10, 0);
     catch (const DivideByZeroException &e) 
        std::cerr << "Error: " << e.what() << std::endl;
    

    return 0;


并发编程中的错误处理

在多线程编程中,线程间的通信和错误处理通常比较复杂。您可以使用C++异常处理机制来处理并发编程中的错误。例如,当一个线程遇到错误时,您可以将异常包装在std::exception_ptr中,并在其他线程中重新抛出和处理该异常。

#include <iostream>
#include <thread>
#include <mutex>
#include <exception>
#include "ThreadException.h"

std::mutex mtx;
std::exception_ptr globalExceptionPtr = nullptr;

void worker() 
    try 
        // Do some work that may throw an exception
        throw ThreadException("Error occurred in worker thread");
     catch (...) 
        std::lock_guard<std::mutex> lock(mtx);
        globalExceptionPtr = std::current_exception();
    


int main() 
    std::thread t(worker);
    t.join();

    if (globalExceptionPtr) 
        try 
            std::rethrow_exception(globalExceptionPtr);
         catch (const ThreadException &e) 
            std::cerr << "Error: " << e.what() << std::endl;
        
    

    return 0;


资源初始化失败

在进行资源初始化时(如数据库连接、外部设备访问等),可能会遇到错误。使用C++异常处理机制可以优雅地处理这些错误。例如,当数据库连接失败时,您可以抛出一个自定义的DatabaseConnectionException异常。

#include <iostream>
#include "DatabaseConnectionException.h"

void connectToDatabase() 
    // Attempt to connect to the database

    // If connection fails, throw an exception
    throw DatabaseConnectionException("Failed to connect to the database");


int main() 
    try 
        connectToDatabase();
     catch (const DatabaseConnectionException &e) 
        std::cerr << "Error: " << e.what() << std::endl;
    

    return 0;


通过以上示例,您可以看到C++异常处理机制在各种使用场景中的应用。在实际编程中,您可以根据项目需求灵活运用异常处理机制,使代码更加健壮和易于维护。同时,需要注意在性能关键和资源受限的场景中,过度依赖异常处理可能会导致性能下降和资源浪费,应该在适当的场景中使用异常处理。


结语

在这篇博客中,我们详细介绍了C++异常处理机制的基本概念、核心组成部分(try、catch、throw),以及如何创建和使用自定义异常类。
我们还探讨了一些C++异常处理的最佳实践,以帮助您编写出更加可维护、可靠的代码。
希望通过本文的介绍,您能够掌握C++异常处理的基本知识,并在实际编程中更好地应对各种异常情况。祝您编程愉快!

异常

第十五章 异常

要想创建健壮的系统,它的每一个构件都必须是健壮的。

异常概念

C++的异常处理机制基于 Ada,Java 中的异常处理则建立在 C++的基础之上(尽管看上去更像 Object Pascal)。

基本异常

异常参数

所有标准异常类都有两个构造器:一个是无参构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器。

Throwable 是异常类型的根类。

自定义异常

对异常来说,最重要的部分就是类名。

异常与记录日志

对于异常类来说,getMessage() 方法有点类似于 toString() 方法。

异常声明

不过还是有个能“作弊”的地方:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。

捕获所有异常

多重捕获

通过 Java 7 的多重捕获机制,可以将不同类型的异常使用“或”将它们组合起来,只在一个 catch 块中使用:

try 
    x();
 catch (Except1 | Except2 | Except3 | Except4 e) 
    process();

栈轨迹

printStackTrace() 方法所提供的信息可以通过 getStackTrace() 方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一桢。元素 0 是栈顶元素,并且是调用序列中的最后一个方法调用(这个 Throwable 被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。

重新抛出异常

只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用 filInStackTrace() 方法,这将返回一个 Throwable 对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。调用 fillInStackTrace() 的那一行就成了异常的新发生地了。

精准的重新抛出异常

Java 7 开始,允许抛出更具体的异常,即 catch 中捕捉的是父类,catch 块中可以抛出子类。

异常链

Throwable 的子类中,只有三种基本的异常类提供了带 cause 参数的构造器。它们是 Error(用于 Java 虚拟机报告系统错误)、Exception 以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用 initCause0 方法而不是构造器。

DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException());
throw dfe;

Java 标准异常

Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两种类型(指从 Throwable 继承而得到的类型):Error 用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception 是可以被抛出的基本类型,在 Java 类库、用户方法以及运行时故障中都可能抛出 Exception 型异常。所以 Java 程序员关心的基类型通常是 Exception

异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。异常并非全是在 java.lang 包里定义的;有些异常是用来支持其他像 util、net 和 io 这样的程序包,这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。

特例:RuntimeException

RuntimeException 类型的异常(或者任何从 RuntimeException 继承的异常),也被称为“不受检查异常”。这种异常属于错误, 代表的是编程错误,将被自动捕获。

只能在代码中忽略 RuntimeException(及其子类)类型的异常,因为所有受检查类型异常的处理都是由编译器强制实施的。

异常限制

当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。

异常限制对构造器不起作用。

一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。

Try-With-Resources 用法

Java 7 引入了 try-with-resources 语法。

try-with-resources 定义子句中创建的对象(在括号内)必须实现 java.lang.Autocloseable 接口,这个接口有一个方法,close()。当在 Java 7 中引入 AutoCloseable 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 Stream 对象。

规范头中定义的每个对象都会在 try 语句块运行结束之后调用 close() 方法。

Java 5 中的 Closeable 已经被修改,修改之后的接口继承了 AutoCloseable 接口。所以所有实现了 Closeable 接口的对象,都支持了 try-with-resources 特性。

不能在资源规范头中定义了一个不是 AutoCloseable 的对象。

异常匹配

抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。

查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。

其他可选方式

异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。

所有模型都是错误的,但有些是能用的。

好的程序设计语言能帮助程序员写出好程序,但无论哪种语言都避免不了程序员用它写出了坏程序。

异常指南

应该在下列情况下使用异常:

  1. 尽可能使用 try-with-resource
  2. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
  3. 解决问题并且重新调用产生异常的方法。
  4. 进行少许修补,然后绕过异常发生的地方继续执行。
  5. 用别的数据进行计算,以代替方法预计会返回的值。
  6. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
  7. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
  8. 终止程序。
  9. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
  10. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)

以上是关于C++异常处理:掌握高效健壮代码的秘密武器的主要内容,如果未能解决你的问题,请参考以下文章

高效管理项目的秘密武器:累积流图

代码健壮性

手把手写C++服务器(34):高并发高吞吐IO秘密武器——epoll池化技术两万字长文

Linux C/C++ 崩溃诊断大师:解锁软件问题定位与修复的秘密武器

深入理解C++中的异常处理机制

中断 Hwi:提高鸿蒙轻内核系统实时性及执行效率的秘密武器