C++异常

C++异常_第1张图片
慢慢来,谁还没有一个努力的过程。


文章目录

  • 概念
  • 异常的抛出和匹配原则
    • 常见的匹配异常的方式
  • 函数调用链中异常栈展开匹配原则
  • 异常的重新抛出
  • 异常安全及规范
    • 异常安全
    • 异常规范
  • 自定义异常体系
  • C++标准库异常体系
  • 总结


概念

C++异常_第2张图片

简单来说,异常就是一种没有达到我们预想的一种情况或者是出现了错误的情况,在C语言中应对处理错误的方式要么是终止程序,要么就是返回错误码。一般来说都是直接返回错误码,但是在出现一些严重错误的时候就会直接终止掉程序。

在C++中,异常是指在程序执行期间发生的意外或异常情况,可能导致程序无法正常执行的事件。当异常发生时,程序可以通过异常处理机制来捕获并处理异常,以避免程序崩溃或产生不可预测的结果。以下是一些有关异常的基本概念:

概念 说明
异常抛出(Throwing Exceptions) 异常抛出是指在代码中发现异常情况时,通过使用throw关键字来抛出一个异常对象。抛出异常后,程序将立即跳转到合适的异常处理代码。
异常捕获(Catching Exceptions) 异常捕获是指使用try-catch语句块来捕获并处理抛出的异常。在try块中,可能出现异常的代码被包裹起来。如果在try块中的代码抛出了异常,程序将跳转到catch块,其中可以对异常进行处理。
异常对象(Exception Objects) 异常对象是用于表示特定异常情况的C++对象。当抛出异常时,可以使用任何类型的对象作为异常对象,包括内置类型、自定义类型或标准库提供的异常类。
异常处理(Exception Handling) 异常处理是指在捕获异常时执行的一系列操作。异常处理可以包括打印错误消息、记录日志、重新抛出异常或采取其他适当的措施来处理异常情况。
异常规范(Exception Specification) 异常规范是一种在函数声明中指定函数可能抛出的异常类型的方式。它可以使用throw关键字和异常类型列表来声明函数可能抛出的异常。然而,自C++11起,异常规范已不再是强制性的,取而代之的是更加灵活的异常处理方式。
标准异常类(Standard Exception Classes) C++标准库提供了一组异常类,这些类定义了常见的异常类型,并提供了有关异常的信息。例如,std::exception类是所有标准异常类的基类,其他异常类如std::runtime_error和std::logic_error派生自它们。

使用异常处理机制可以使程序更加健壮和可靠,能够优雅地处理异常情况,提高程序的容错性和可维护性。但在使用异常时,需要合理选择何时抛出异常、如何捕获和处理异常,以及在处理异常时遵循良好的异常处理实践。

异常的抛出和匹配原则

C++中的异常处理机制涉及两个关键方面:异常的抛出和异常的匹配。异常的抛出是在代码中发现异常情况时使用throw关键字抛出异常对象。而异常的匹配是通过try-catch语句块来捕获并处理抛出的异常。

异常的抛出原则:

  1. 只有在出现无法恢复或处理的错误情况时才抛出异常。例如,内存分配失败、打开文件失败等。
  2. 抛出的异常应该是与问题本身相关的具体异常类型或其派生类,以便在异常处理时进行准确的匹配和处理。
  3. 异常对象可以是内置类型、自定义类型或标准库提供的异常类的实例。

异常的匹配原则:

  1. 使用try-catch语句块来捕获和处理可能抛出的异常。try块用于包装可能引发异常的代码。
  2. try-catch语句块中的catch块用于捕获和处理特定类型的异常。每个catch块可以匹配一个或多个异常类型。
  3. 异常类型匹配是通过异常对象的类型进行的。如果抛出的异常类型与catch块中指定的异常类型匹配,则将执行相应的catch块中的代码。
  4. 可以使用多个catch块来处理不同类型的异常。异常类型的匹配按照catch块的出现顺序进行,因此应将特定类型的异常放在更具体的catch块之前,以确保正确的匹配。

如下示例:

#include 

void divide(int num, int den) 
{
    if (den == 0) 
    {
        throw std::runtime_error("Division by zero"); // 抛出异常
    }
    int result = num / den;
    std::cout << "Result: " << result << std::endl;
}

int main() 
{
    try 
    {
        divide(10, 0); // 调用可能抛出异常的函数
    }
    catch (std::runtime_error& e) 
    {
        std::cout << "Exception caught: " << e.what() << std::endl; // 捕获并处理异常
    }
    return 0;
}

在上述示例中,divide函数用于执行整数除法。如果除数为零,则抛出std::runtime_error类型的异常。在main函数中,我们调用divide函数,并使用try-catch块捕获可能抛出的异常。在catch块中,我们处理捕获到的异常并输出错误消息。

C++异常_第3张图片

常见的匹配异常的方式

在异常处理中,catch块有几种不同的方式来匹配和处理异常。下面是常见的几种catch块的方式:

  • 单一异常类型的catch块:这种方式的catch块只能捕获指定类型的异常。当抛出的异常类型与catch块中指定的类型匹配时,该catch块将被执行。例如:
try {
    // 可能引发异常的代码
} catch (ExceptionType& e) {
    // 处理特定类型的异常
}
  • 多个异常类型的catch块:这种方式的catch块允许处理多个不同类型的异常。catch块按照特定到一般的顺序排列,最具体的异常类型应该放在前面,最一般的异常类型应该放在最后。例如:
try {
    // 可能引发异常的代码
} catch (ExceptionType1& e) {
    // 处理类型1的异常
} catch (ExceptionType2& e) {
    // 处理类型2的异常
} catch (ExceptionType3& e) {
    // 处理类型3的异常
}
  • 基类异常的catch块:这种方式的catch块可以捕获基类异常及其派生类的异常。如果抛出的异常是派生类,它可以匹配基类异常的catch块。例如:
try {
    // 可能引发异常的代码
} catch (BaseException& e) {
    // 处理基类异常
}
  • 通用的catch块:这种方式的catch块被称为通用的catch块,使用省略号...表示。它可以捕获任意类型的异常,包括内置类型、自定义类型和标准库提供的异常类型。通常,通用的catch块用于处理未知类型的异常或进行一般性的错误处理。例如:
try {
    // 可能引发异常的代码
} catch (...) {
    // 处理任意类型的异常
}

在使用catch块时,可以根据实际需求选择适当的方式。单一异常类型的catch块适用于处理特定的异常情况,多个异常类型的catch块可以根据不同的异常类型执行不同的处理逻辑,基类异常的catch块可以捕获一组相关的异常,而通用的catch块可以用于处理未知类型的异常或进行通用的错误处理。

需要注意的是,catch块的顺序很重要。如果将通用的catch块放在最前面,它将捕获所有的异常,而后续的catch块将永远无法执行。因此,应该根据异常类型的特殊性和一般性,合理地排列catch块的顺序。

总之,异常的抛出和匹配原则涉及确定何时抛出异常以及如何使用try-catch语句块来捕获和处理异常。遵循良好的异常处理原则可以提高程序的可靠性和可维护性,使其能够优雅地处理异常情况。

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

在函数调用链中,当异常被抛出时,异常处理机制会自动展开函数调用栈,寻找匹配的catch块来处理异常。这个过程被称为异常栈展开(stack unwinding)。以下是异常栈展开和匹配的一些原则:

异常栈展开过程:

  1. 当异常被抛出时,程序会从当前函数开始展开函数调用栈,查找匹配的catch块。
  2. 如果当前函数没有合适的catch块来处理异常,那么异常会被传递给当前函数的调用者。
  3. 这个过程会一直持续到找到匹配的catch块或者到达main()函数(如果没有找到合适的处理方式)。
  4. 如果到达main函数的栈,依旧没有匹配的,则终止程序。但是在实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  5. 在展开过程中,如果某个函数中存在资源(如堆上分配的内存、打开的文件等),那么会自动调用析构函数来进行资源的清理。

C++异常_第4张图片

如下示例:

#include 

void funcB() 
{
    throw std::runtime_error("Exception in funcB");
}

void funcA() 
{
	funcB();
}

int main() 
{
    try 
    {
        funcA();
    }
    catch (std::exception& e) 
    {
        std::cout << "Caught exception in main: " << e.what() << std::endl;
    }
    return 0;
}

在上述示例中,函数funcB抛出一个std::runtime_error类型的异常。funcA函数调用了funcB,但是并不捕获这个异常。最终,异常被main函数的try-catch块捕获并处理。

C++异常_第5张图片

总之,在函数调用链中,异常栈展开和匹配原则决定了异常如何在函数之间传递,并找到合适的catch块进行处理。通过合理地使用try-catch块和异常类型的匹配,可以实现对异常的适当处理和恢复。

异常的重新抛出

在异常处理过程中,有时候我们希望在捕获异常后将其重新抛出,以便在更高层次的代码中进行进一步的处理或传播。异常的重新抛出可以通过在catch块中使用throw语句来实现。如下代码:

void performDatabaseOperation() 
{
    // 模拟数据库操作引发的异常
    throw std::runtime_error("Database exception occurred");
}

void performNetworkOperation() 
{
    try 
    {
        performDatabaseOperation();
    }
    catch (std::runtime_error& e) 
    {
        std::cout << "Caught database exception in performNetworkOperation: " << e.what() << std::endl;
        // 处理数据库异常后,重新抛出异常
        throw;  // 重新抛出异常
    }
}

void processRequest() 
{
    try 
    {
        performNetworkOperation();
    }
    catch (std::runtime_error& e) 
    {
        std::cout << "Caught network exception in processRequest: " << e.what() << std::endl;
        // 处理网络异常后,重新抛出异常
        throw;  // 重新抛出异常
    }
}

int main() 
{
    try 
    {
        processRequest();
    }
    catch (std::runtime_error& e) 
    {
        std::cout << "Caught exception in main: " << e.what() << std::endl;
        // 处理最终的异常或进行传播
    }
    return 0;
}

在上述示例中,performDatabaseOperation函数模拟数据库操作引发的异常。在performNetworkOperation函数中,我们调用performDatabaseOperation并在catch块中捕获并处理数据库异常,然后使用throw语句重新抛出异常。在更高层次的processRequest函数中,我们调用performNetworkOperation并在catch块中捕获并处理网络异常,然后使用throw语句重新抛出异常。最后在main函数中,我们调用processRequest并在最外层的catch块中捕获并处理最终的异常。

C++异常_第6张图片

需要注意的是,在重新抛出异常时,可以选择不指定异常对象,即使用throw;而不带参数。这将导致当前捕获的异常对象被重新抛出,保持异常的类型和信息不变。另外,可以在catch块中对异常进行处理,然后在重新抛出之前执行一些其他操作。这样可以在重新抛出异常之前记录日志、释放资源或执行其他清理操作。

总结起来,通过重新抛出异常,我们可以在多个层次的代码中进行适当的处理或传播。每个层级可以处理自己的异常情况,并将异常传递给更高层次的代码进行进一步的处理。这样可以实现更好的错误处理和异常传播机制。

异常安全及规范

异常安全

异常安全是指在程序中处理异常时,确保对象和资源的正确管理和状态的一致性,以防止发生内存泄漏或对象状态不一致等问题。异常安全性通常要考虑以下三个级别:

  1. 基本异常安全:确保在发生异常时不会出现资源泄漏。即当异常发生时,资源应该被适当释放,以防止内存泄漏等问题。

  2. 强异常安全:除了基本异常安全性要求外,还要确保程序状态不变。即当发生异常时,对象的状态应该回滚到异常发生之前的状态,不会出现部分修改或不一致的情况。

  3. 不抛出异常:确保函数不会抛出异常。这意味着函数在任何情况下都不会引发异常,并且可以安全地在异常不可用的环境中使用。

例如在构造函数完成对象的构造和初始化时,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。或则在是析构函数完成资源的清理时,最好不要在析构函数内抛出异常,否则可能导致资源泄漏。(内存泄漏、句柄未关闭等)

为实现异常安全,可以使用RAII(资源获取即初始化)技术,通过在构造函数中获取资源,在析构函数中释放资源,可以确保资源的正确管理。当发生异常时,对象的析构函数会被自动调用,从而保证资源的释放。或者提前检查和处理可能引发异常的情况,在执行可能引发异常的操作之前,进行必要的检查和预处理,以确保操作可以安全地执行。这样可以减少异常发生的可能性。

综上所述,异常安全是一种重要的编程原则,可以确保程序在异常发生时能够正确处理资源和状态,避免出现内存泄漏和对象状态不一致等问题。使用适当的技术和设计模式,可以提高程序的异常安全性。

异常规范

异常规范是一种在函数声明中指定函数可能抛出的异常类型的方法。它定义了函数可能引发的异常的类型,以便程序员在调用该函数时可以了解可能发生的异常情况。在 C++ 中,异常规范可以通过在函数声明的括号后面使用 throw 关键字和异常类型列表来指定。例如:

void Function() throw(ExceptionType1, ExceptionType2);

上述代码表示 Function 函数可能会抛出 ExceptionType1 和 ExceptionType2 类型的异常。在早期版本的 C++ 中,异常规范被广泛使用,并且可以在函数的实现中进行检查以确保函数不会引发未在异常规范中声明的异常。但是,在 C++11 标准中,异常规范被视为可选项,不再作为强制性的语言特性。这是因为异常规范在实践中往往难以正确使用,并且可能导致代码的不灵活性和可维护性问题。

相比于异常规范,现代 C++ 推荐使用异常安全的编程技术和设计原则来处理异常,如 RAII(资源获取即初始化)和智能指针等。这些技术可以更好地管理资源,并确保在异常发生时正确处理资源的释放,而无需显式指定异常规范。

需要注意的是,虽然异常规范在现代 C++ 中不再是强制性的语言特性,但在特定的上下文中仍然可能有用。例如,某些库或框架可能会使用异常规范来指定其函数可能引发的异常类型,并提供给用户方便的异常处理和文档说明。在这种情况下,遵循库或框架的异常规范是一种好的实践。

自定义异常体系

在许多编程语言中,包括Java和C++,都支持自定义异常体系。通过创建自定义异常类,可以根据特定的应用程序需求和逻辑来定义和抛出异常。自定义异常体系提供更具语义和逻辑的异常处理机制,以满足特定应用程序的需求。以下是自定义异常体系的几个重要作用:

作用 说明
错误信息的传递和记录 通过自定义异常体系,可以为不同类型的错误或异常定义特定的异常类。每个异常类可以携带有关错误的详细信息,如错误类型、错误原因、位置等。这样,当异常发生时,可以将详细的错误信息传递给异常处理器,便于定位和调试问题。
异常处理的灵活性 通过自定义异常体系,可以根据应用程序的需求定义不同层次或类型的异常,从而为不同的异常情况提供特定的处理逻辑。例如,可以为致命错误定义一种异常类型,而为可恢复错误定义另一种异常类型。这样,异常处理器可以根据异常类型采取不同的行动,如回滚操作、重试、日志记录等。
代码结构的清晰性 自定义异常体系可以提供更结构化和模块化的代码组织方式。通过将相关的异常类组织在一起,可以更清晰地表达代码逻辑和异常情况之间的关系。这有助于代码的可读性和可维护性,并使开发人员能够更好地理解和处理异常情况。
异常处理的一致性 通过自定义异常体系,可以定义一致的异常处理规范和契约。在应用程序的不同部分使用相同的异常类和命名约定,可以提供一致的异常处理方式。这有助于开发人员更好地理解异常处理的逻辑,并减少潜在的混淆和错误。
异常信息的本地化和国际化 通过自定义异常体系,可以为不同的异常类型提供本地化和国际化支持,以适应不同地区和语言环境的需求。异常消息可以根据用户的语言偏好进行翻译和本地化,提供更友好和可理解的错误信息。

如下代码:

#include 

// 自定义基础异常类
class MyAppException : public std::exception 
{
public:
    MyAppException(const char* message) : message_(message) {}

    const char* what() const
    {
        return message_;
    }

private:
    const char* message_;
};

// 自定义具体异常类
class MySpecificException : public MyAppException 
{
public:
    MySpecificException(const char* message) : MyAppException(message) {}
};

// 使用自定义异常
class MyClass 
{
public:
    void performOperation() 
    {
        // 模拟发生异常的操作
        throw MySpecificException("Something went wrong");
    }
};

int main() 
{
    try 
    {
        MyClass obj;
        obj.performOperation();
    }
    catch (const MyAppException& e) 
    {
        std::cout << "Caught custom exception: " << e.what() << std::endl;
    }

    return 0;
}

在上述示例中,我们首先定义了一个基础的自定义异常类 MyAppException,它继承自 C++ 标准库中的 std::exception。然后,我们定义了一个具体的自定义异常类 MySpecificException,它继承自 MyAppException。

在 MyClass 类中,我们声明了一个方法 performOperation(),该方法可能会抛出 MyAppException。在该方法中,我们模拟了发生异常的情况,通过抛出 MySpecificException 来触发异常。在 main() 函数中,我们创建了 MyClass 的实例,并调用了 performOperation() 方法。在 catch 块中,我们捕获并处理了 MyAppException 及其子类的异常,打印出异常消息。

C++异常_第7张图片

总体而言,自定义异常体系可以提供更精确、可读性更强、逻辑更清晰的异常处理机制。它使开发人员能够更好地管理和处理异常情况,提高应用程序的健壮性和可靠性。

C++标准库异常体系

C++标准库提供了一组内置的异常类,构成了C++的异常体系。这些异常类都继承自std::exception类,因此可以使用相同的异常处理机制来捕获和处理它们。以下是C++标准库中一些常见的异常类:

异常类 说明
std::exception std::exception是所有标准库异常类的基类。它定义了一个what()函数,用于返回异常的描述信息。
std::bad_alloc std::bad_alloc表示内存分配错误,通常在无法分配所需内存时抛出。
std::bad_cast std::bad_cast表示类型转换错误,通常在dynamic_cast失败时抛出。
std::logic_error std::logic_error是逻辑错误的基类异常,它们通常由程序的逻辑错误导致,而不是运行时错误。
std::runtime_error std::runtime_error是运行时错误的基类异常,它们通常由底层系统或运行时环境发生的错误导致。
std::overflow_error std::overflow_error表示溢出错误,通常用于表示算术运算或类型转换导致溢出。
std::underflow_error std::underflow_error表示下溢错误,通常用于表示算术运算或类型转换导致下溢。
std::range_error std::range_error表示范围错误,通常用于表示超出了某个数据类型的有效范围。

除了以上列出的异常类,C++标准库还提供了其他一些异常类,用于处理不同的错误情况。这些异常类位于< stdexcept >头文件中,并且您也可以通过继承std::exception类来创建自定义的异常类。在异常处理时,可以使用try-catch语句来捕获并处理这些异常。

总结

文章介绍了C++中的异常机制,合理正确的使用异常机制可以帮助我们分离正常代码和错误处理逻辑和集中处理错误,还可以传递和记录错误信息,提供异常安全保证。

然而在使用异常时,需要注意合理使用,不正确的使用异常可能会造成性能开销,还会增加代码的复杂性。并且异常不适合所有场景。在某些特定的应用程序或系统中,如嵌入式系统或实时系统,异常处理机制可能不被支持或不可行。在这些情况下,使用其他错误处理策略可能更合适。

总体而言,异常机制是一种强大的错误处理机制,可以提高代码的可读性、可维护性和健壮性。但在使用异常时,需要注意合理使用,避免过度使用和滥用异常,以确保程序的性能和可靠性。

码文不易,如果文章对你有帮助的话就来一个三连呗

C++异常_第8张图片

你可能感兴趣的:(C++,c++,开发语言)