2023-5-22-C++异常处理机制学习



🍿*★,°*:.☆( ̄▽ ̄)/$:*.°★* 🍿

💥💥💥欢迎来到🤞汤姆🤞的csdn博文💥💥💥
💟💟喜欢的朋友可以关注一下,下次更新不迷路💟💟
😆😆😆私聊获取个人订阅号哦,欢迎订阅共同学习😆😆😆
💖💖💖💖可以加入大家庭群聊,一起学习天天有福利💖💖💖💖





🍬本文摘要

在这里插入图片描述

这篇文章是为了多角度学习一下C++异常处理机制,打算后续写一个高级全面的日志管理工具。




😉一、C++异常处理机制介绍

C++ 异常处理机制是一种用于在程序运行时检测错误并进行相应处理的技术。当程序执行过程中遇到异常情况,比如计算错误、内存分配失败等,就会抛出一个异常对象。该对象包含有关异常的信息,包括异常类型和位置。

异常处理机制是由 try-catch 块组成的。try 块中包含可能引发异常的代码,而 catch 块则用于处理特定类型的异常。可以为每个 try 块指定多个 catch 块,以便处理不同类型的异常。

当 try 块中的代码引发异常时,控制流将转移到与之匹配的 catch 块。如果没有匹配的 catch 块,则程序将终止,并显示错误消息。

除了 try-catch 块外,还有一些其他的异常处理机制,例如 throw 操作符和 noexcept 关键字。throw 操作符用于抛出异常,而 noexcept 关键字用于指示函数不会抛出异常。这些机制可以更精细地控制异常处理过程。

需要注意的是,在使用异常处理机制时,需要小心确保资源的正确释放,以避免内存泄漏等问题。

try {
    // 可能抛出异常的代码
} catch (exception& e) {
    // 处理异常
}


🎉二、抛出和捕获异常的方式

在 C++ 中,抛出和捕获异常的方式主要有以下两种:

  1. 使用 try-catch 块:try 块中包含可能引发异常的代码,而 catch 块则用于处理特定类型的异常。可以为每个 try 块指定多个 catch 块,以便处理不同类型的异常。

示例代码:

try {
    // 可能会引发异常的代码
    throw MyException();  // 抛出自定义异常
}
catch (MyException& e) {  // 处理自定义异常
    // 处理异常的代码
}
catch (std::exception& e) {  // 处理标准异常
    // 处理异常的代码
}
catch (...) {  // 处理所有其他异常
    // 处理异常的代码
}
  1. 使用 throw 操作符:throw 操作符用于抛出异常。通过 throw 抛出的异常可以被上层调用者捕获并处理。

示例代码:

if (someCondition) {
    // 抛出 std::runtime_error 异常
    throw std::runtime_error("An error occurred.");
}

🎉三、C++异常有几种

C++ 异常主要可以分为三种类型:

  1. 标准异常(std::exception):是所有标准 C++ 异常的基类,包括 std::runtime_error、std::logic_error 等。

  2. 自定义异常:程序员可以根据自己的需求创建异常类来实现特定的异常处理逻辑。

  3. 系统异常:由操作系统或底层库抛出的异常,例如访问非法内存地址、除零等。这些异常不能被捕获并处理,只能通过遵循编程最佳实践避免它们的发生。


🐱‍🚀四、标准C++异常有几种

标准C++异常有15种,其中13种是派生自std::exception的标准异常类,另外两种是std::bad_exception和std::nested_exception。

这些标准异常类的区别在于它们所表示的错误类型不同。例如,std::invalid_argument异常表示对一个函数传递了无效的参数,而std::out_of_range异常则表示访问了一个超出范围的元素。std::bad_alloc异常表示内存分配失败,而std::logic_error异常表示程序逻辑错误。以下是15中标准C++异常:
以下是 C++ 标准库中 15 种标准异常及其含义:

  1. std::exception:所有标准异常的基类,可以被用来捕获任意一个标准异常。
  2. std::bad_alloc:在动态内存分配失败时抛出。
  3. std::bad_cast:在使用动态类型转换(dynamic_cast)时,如果转换目标与对象类型不兼容,则抛出。
  4. std::bad_exception:当函数抛出了一个未捕获的异常时,会调用 std::unexpected() 函数,如果 std::unexpected() 函数也抛出了异常,则会进一步调用 std::terminate() 函数。std::bad_exception 就是 std::unexpected() 函数抛出的异常。
  5. std::bad_typeid:在使用 typeid 运算符获取对象类型信息时,如果对象是空指针或者不是多态类型,则会抛出此异常。
  6. std::logic_error:逻辑错误,通常在程序运行期间检测到。从 std::exception 继承而来,有两个子类:std::domain_error 和 std::invalid_argument。
  7. std::domain_error:某些操作的参数超出了定义域范围。
  8. std::invalid_argument:提供给函数的参数无效。
  9. std::length_error:当试图创建大于 max_size 的对象时抛出。
  10. std::out_of_range:访问数组、字符串或容器等序列类型时,下标越界时抛出。
  11. std::runtime_error:运行时错误,通常在程序运行期间检测到。从 std::exception 继承而来,有四个子类:std::overflow_error、std::range_error、std::underflow_error 和 std::system_error。
  12. std::overflow_error:算术上溢出(如整数相加结果超过了类型的最大值)时抛出。
  13. std::range_error:尝试存储超过类型所能表示的数据(如负数开根号)时抛出。
  14. std::underflow_error:算术下溢(如整数相减结果超过了类型的最小值)时抛出。
  15. std::system_error:涉及底层操作系统或者 C++ 标准库的错误,比如文件打开失败等。含有一个 error_code 类型的成员变量表示错误码。

std::bad_exception和std::nested_exception是除标准异常以外的两个特殊异常类。std::bad_exception用于表示抛出了一个未知的异常类型,而std::nested_exception用于捕获、保存并再次抛出当前异常的信息。


🎂五、怎么实现自定义异常

在 C++ 中,可以通过继承标准异常类 std::exception 来实现自定义异常。具体步骤如下:

  1. 定义一个新的异常类,并从 std::exception 继承。
class MyException : public std::exception {
public:
    // 构造函数可以接受一个字符串作为异常信息
    MyException(const std::string& msg) : message(msg) {}

    // 继承自 std::exception 的虚函数 what(),用于返回异常信息
    const char* what() const noexcept override {
        return message.c_str();
    }

private:
    std::string message;
};
  1. 抛出自定义异常对象。
void foo() {
    throw MyException("Something went wrong!");
}
  1. 在 catch 块中捕获自定义异常。
try {
    foo();
} catch (const MyException& e) {
    std::cerr << "Exception caught: " << e.what() << '\n';
}

在这个例子中,我们定义了一个名为 MyException 的异常类,它包含一个字符串类型的成员变量 message 用于存储异常信息。该类同时重载了 what() 函数,以便在抛出异常时提供异常信息。

在函数 foo() 中,我们使用 throw 语句抛出一个 MyException 类型的对象并传递一个字符串作为异常信息。

try-catch 块中,我们编写了一个 catch 块来捕获 MyException 类型的异常,打印出异常信息。


🥩六、注意事项

以下是在C++中处理异常时需要注意的事项:

  1. 在程序中使用try-catch块捕获异常,避免程序因未处理的异常而崩溃。
  2. 异常应该被抛出和捕获在适当的位置,即在能够处理它们的地方抛出异常,然后在合适的位置进行捕获。
  3. 不要在析构函数中抛出异常,因为这可能导致未定义的行为。
  4. 使用特定的异常类型来组织代码,并且仅在必要的情况下派生新的异常类型。
  5. 将异常信息记录到日志文件中,以便更好地跟踪异常发生的原因。
  6. 不要在循环中捕获异常,因为这会影响程序的性能。
  7. 避免在同一try块中嵌套多个catch块,因为这可能会使代码变得混乱难懂。
  8. 在尝试修复异常之前,请确保已经详细了解了异常的根本原因。
  9. 不要忽略异常,即使它们似乎可以安全地被忽略。这可能导致程序在后续执行中出现问题。
  10. 小心处理内存分配的异常,如果没有正确的处理,这可能导致内存泄漏或其他严重的问题。

这些是处理C++异常时需要注意的一些重要事项,但并不是全部。处理异常时最重要的原则是谨慎和小心,以确保程序的安全性和稳定性。


🍚七、什么是异常安全性?如何保证程序具有异常安全性?

在C++中,异常安全性是指一个程序在发生异常后仍能保持正确和健壮的行为。具有良好的异常安全性意味着即使出现异常,程序也不会崩溃或导致资源泄漏等问题。

要确保程序具有异常安全性,可以采取以下措施:

  1. 使用RAII(Resource Acquisition Is Initialization)技术来管理资源,例如使用智能指针、容器和文件句柄等。这些资源对象在其析构函数中释放资源,无论是否发生异常。

  2. 避免手动管理内存,使用标准库容器、字符串和算法等高级语言特性进行操作。

  3. 在抛出异常时不要改变对象状态,因为这可能导致未定义的行为。可使用copy-and-swap技术避免状态的改变。

  4. 将资源分配和使用的代码块封装在单独的函数中,并将其声明为noexcept,这样可以确保该函数不会引发异常

  5. 在处理异常时,必须对所有可能引发异常的代码进行评估,并在恢复正常执行之前确保任何资源都已正确释放。

  6. 在编写代码时,考虑到异常情况并进行适当的测试。

通过以上措施可以提高程序的异常安全性,在开发大型项目时,保持良好的异常安全性对于代码的可维护性和稳定性至关重要。


🥠八、RAII技术在异常处理中的应用

RAII(Resource Acquisition Is Initialization)技术是一种C++编程技术,它通过在对象的构造函数中获取资源并在析构函数中释放资源来管理资源。

在异常处理中,RAII技术可以确保资源在出现异常时被正确地释放。当抛出异常时,程序会自动执行已经创建的对象的析构函数,从而释放对象占用的资源,无需手动处理异常。这种自动化的行为避免了可能发生的资源泄漏和内存泄漏问题。通常情况下,RAII技术与C++标准库中的智能指针结合使用,以确保动态分配的内存得到正确地释放。

例如,在操作文件时,我们可以使用std::ofstream来打开文件,并将其封装在一个类中:

class File {
public:
    File(const std::string& filename) : _file(filename) {
        if (!_file.is_open()) {
            throw std::runtime_error("failed to open file");
        }
    }

    ~File() {
        if (_file.is_open()) {
            _file.close();
        }
    }

    void write(const std::string& s) {
        _file << s;
    }

private:
    std::ofstream _file;
};

void foo() {
    File f("test.txt");
    f.write("hello, world");
}

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

在上面的示例中,我们定义了一个File类来封装文件操作,构造函数中打开文件并检查是否成功打开,析构函数中关闭文件。

在foo函数中,我们创建了一个File对象来进行文件写操作。无论在写入数据时是否发生异常,File对象都会被正确地销毁,并自动调用析构函数来关闭文件。这保证了程序具有异常安全性。

总之,RAII技术能够有效地提高代码的可靠性和可读性,使得程序的异常处理更加简单和安全。


🍚九、std::exception_ptr的作用和使用方法

std::exception_ptr是一个指向异常的指针,它允许将异常在程序的不同部分传递和保存,从而实现跨越不同层次的异常处理。

使用方法如下:

  1. 在抛出异常时使用 std::make_exception_ptr()std::current_exception() 函数创建 std::exception_ptr 对象。
  2. std::exception_ptr 作为参数传递给其他函数或对象以传递异常信息。
  3. 在需要重新抛出异常时,可以使用 std::rethrow_exception() 函数将 std::exception_ptr 转换回原始的异常对象。
  4. 在捕获异常时,可以使用 std::exception_ptr 指示是否发生了异常,并使用 std::rethrow_if_nested() 函数检查是否存在嵌套异常。

例如,以下代码演示了如何使用 std::exception_ptr 来传递异常信息:

void foo() {
    try {
        // some code that may throw an exception
    } catch (...) {
        std::exception_ptr eptr = std::current_exception();
        bar(eptr); // pass the exception to another function
    }
}

void bar(std::exception_ptr eptr) {
    if (eptr) {
        // rethrow the exception
        std::rethrow_exception(eptr);
    } else {
        // handle the case where no exception was thrown
    }
}

🍳参考文献

🧊文章总结

提示:这里对文章进行总结:

  本文讲了关于C++异常处理机制的一些内容,希望大家有所收获。






更多好文推荐

🍸2021-4月Python 机器学习——中文新闻文本标题分类
🍹2021年4月-(计算机网络)小型校园网络模拟搭建,最全最准确版
🍺2022-10-31-基于用户的协同过滤推荐算法实现+MAE+RMSE
🍻2022-11-28-大数据可视化,特征维度大于50
🥂2023-3-9-一篇简短的文章把C++左右值关系讲的透透彻彻

上一篇
End
下一篇
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汤姆z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值