C++如何使用标准异常类std::exception

使用std::exception可构建健壮代码,其继承体系提供标准错误处理机制;应合理使用标准异常类如std::invalid_argument,并在需传递额外信息时自定义异常类;避免使用已废弃的异常规范,改用noexcept;通过RAII等技术保证异常安全,防止资源泄漏。

c++如何使用标准异常类std::exception

C++中使用

std::exception

,本质上是为了构建更健壮、更易于维护的代码。它提供了一种标准的、结构化的方式来处理程序运行期间可能出现的各种错误。不必每次都手动构建错误处理机制,而是可以依赖于这个预定义的类及其派生类。

使用

std::exception

,核心在于理解其继承体系,并在适当的时候抛出和捕获异常。

解决方案:

C++标准库提供了一系列从

std::exception

派生的异常类,用于表示不同类型的错误。例如:

立即学习“C++免费学习笔记(深入)”;

std::bad_alloc

: 当

new

操作符无法分配内存时抛出。

std::bad_cast

: 当使用

dynamic_cast

进行类型转换失败时抛出。

std::invalid_argument

: 当函数接收到无效参数时抛出。

std::out_of_range

: 当试图访问超出范围的容器元素时抛出。

std::runtime_error

: 用于报告运行时发生的错误。

std::logic_error

: 用于报告程序逻辑上的错误。

以下是一个使用

std::exception

的简单例子:

#include #include int divide(int a, int b) {    if (b == 0) {        throw std::invalid_argument("Division by zero is not allowed.");    }    return a / b;}int main() {    try {        int result = divide(10, 0);        std::cout << "Result: " << result << std::endl;    } catch (const std::invalid_argument& e) {        std::cerr << "Error: " << e.what() << std::endl;        return 1;    } catch (const std::exception& e) {        std::cerr << "An unexpected error occurred: " << e.what() << std::endl;        return 1;    } catch (...) {        std::cerr << "Unknown exception caught!" << std::endl;        return 1;    }    return 0;}

在这个例子中,

divide

函数在除数为零时抛出一个

std::invalid_argument

异常。

main

函数中的

try-catch

块捕获这个异常,并打印错误信息。注意,最后的

catch(...)

可以捕获所有未被前面

catch

块处理的异常,但通常不建议过度使用,因为它会隐藏具体的错误类型。

何时应该自定义异常类?

并非所有错误都需要使用标准异常类。在某些情况下,自定义异常类可能更合适。例如,当需要传递额外的错误信息,或者需要区分特定于应用程序的错误类型时,自定义异常类就显得很有必要。

自定义异常类通常从

std::exception

或其派生类继承,并添加自己的成员变量和方法来存储和访问错误信息。

#include #include #include class MyCustomException : public std::runtime_error {public:    MyCustomException(const std::string& message, int errorCode)        : std::runtime_error(message), errorCode_(errorCode) {}    int getErrorCode() const {        return errorCode_;    }private:    int errorCode_;};int processData(int data) {    if (data < 0) {        throw MyCustomException("Data is invalid.", 1001);    }    return data * 2;}int main() {    try {        int result = processData(-5);        std::cout << "Result: " << result << std::endl;    } catch (const MyCustomException& e) {        std::cerr << "Custom Exception caught: " << e.what()                  << ", Error Code: " << e.getErrorCode() << std::endl;        return 1;    } catch (const std::exception& e) {        std::cerr << "Standard Exception caught: " << e.what() << std::endl;        return 1;    }    return 0;}

在这个例子中,

MyCustomException

继承自

std::runtime_error

,并添加了一个

errorCode_

成员变量来存储自定义的错误代码。

异常规范(Exception Specifications)是否应该使用?

在C++11之前,可以使用异常规范来声明函数可能抛出的异常类型。例如:

void myFunction() throw (std::runtime_error, std::bad_alloc);

这表明

myFunction

可能会抛出

std::runtime_error

std::bad_alloc

异常。然而,异常规范在C++11中被废弃,并在C++17中被移除。现在,只剩下

noexcept

规范,用于声明函数不会抛出任何异常。

使用

noexcept

可以帮助编译器进行优化,并提供更强的异常安全保证。例如:

void myFunction() noexcept;

这表明

myFunction

不会抛出任何异常。如果

myFunction

内部抛出了异常,程序会立即终止(调用

std::terminate

)。

因此,不建议使用C++11之前的异常规范。应该使用

noexcept

来声明不抛出异常的函数。

如何保证异常安全?

异常安全是指在异常抛出时,程序的状态仍然保持一致。这通常需要仔细设计代码,以确保资源得到正确释放,数据结构保持有效。

以下是一些保证异常安全的常用技巧:

RAII (Resource Acquisition Is Initialization): 使用RAII技术来管理资源,例如使用智能指针来自动释放内存。Copy-and-Swap: 在修改对象状态之前,先创建一个副本,然后在副本上进行修改。如果修改过程中发生异常,原始对象的状态不会受到影响。修改完成后,将副本与原始对象进行交换。Strong Exception Safety: 保证操作要么完全成功,要么完全不产生副作用。如果操作失败,程序的状态应该恢复到操作之前的状态。Basic Exception Safety: 保证操作不会导致资源泄漏,并且对象的状态仍然有效。No-Throw Guarantee: 保证操作不会抛出任何异常。

例如,使用RAII技术来管理互斥锁:

#include #include #include class LockGuard {public:    LockGuard(std::mutex& mutex) : mutex_(mutex) {        mutex_.lock();    }    ~LockGuard() {        mutex_.unlock();    }private:    std::mutex& mutex_;};void processData(int data, std::mutex& mutex) {    LockGuard lock(mutex); // Acquire lock    if (data < 0) {        throw std::invalid_argument("Data is invalid.");    }    // Process data    std::cout << "Processing data: " << data << std::endl;}int main() {    std::mutex mutex;    try {        processData(-5, mutex);    } catch (const std::exception& e) {        std::cerr << "Exception caught: " << e.what() << std::endl;        return 1;    }    return 0;}

在这个例子中,

LockGuard

类使用RAII技术来管理互斥锁。当

LockGuard

对象被创建时,互斥锁被锁定。当

LockGuard

对象被销毁时(无论是因为正常退出还是因为异常抛出),互斥锁都会被自动解锁。这确保了互斥锁总是被正确释放,即使在异常情况下也是如此。

总而言之,

std::exception

是C++异常处理的基础。理解其继承体系,合理使用标准异常类,并在必要时自定义异常类,可以帮助编写更健壮、更易于维护的代码。同时,注意保证异常安全,避免资源泄漏和数据损坏。

以上就是C++如何使用标准异常类std::exception的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1475956.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月18日 23:50:32
下一篇 2025年12月18日 23:50:37

相关推荐

  • C++如何使用结构体实现数据封装

    C++中结构体可通过private成员和public接口实现数据封装,如Point示例所示,其与类的核心封装机制相同,主要区别在于默认访问权限:struct成员默认public,class默认private,但功能上等价,选择取决于语义表达与使用场景。 C++中,结构体( struct )同样能实现…

    2025年12月18日
    000
  • C++的结构体和联合体在内存分配和布局上有何关键差异

    结构体为成员分配独立内存,总大小为成员大小之和加填充;联合体所有成员共享同一内存,总大小等于最大成员大小。 C++的结构体( struct )和联合体( union )在内存分配和布局上的核心差异在于它们成员变量的存储方式:结构体为每个成员分配独立的内存空间,而联合体则让所有成员共享同一块内存区域。…

    2025年12月18日
    000
  • C++数组和指针混合使用注意事项

    数组不是指针,但多数表达式中会退化为指向首元素的指针;sizeof和&运算符例外,函数传参时实际传递指针,需额外传长度;多维数组退化为行指针,应正确声明参数类型;推荐使用std::array或std::span避免错误。 在C++中,数组和指针虽然经常可以互换使用,但它们本质上是不同的概念。…

    2025年12月18日
    000
  • C++shared_ptr销毁顺序与引用计数变化

    std::shared_ptr通过引用计数管理资源,拷贝时加1,销毁或重置时减1,计数为0则对象被删除;局部变量逆序销毁,循环引用需用weak_ptr打破,自定义删除器确保资源正确释放。 在C++中,std::shared_ptr 的销毁顺序和引用计数的变化是理解资源管理的关键。它通过引用计数机制实…

    2025年12月18日
    000
  • C++项目移植时如何搭建相同环境

    C++项目移植需确保编译器、依赖库、构建系统和运行时环境一致。使用Conan、vcpkg等包管理器可有效管理第三方依赖版本与链接方式,避免因库差异导致的兼容性问题;通过Docker容器或虚拟机实现构建环境隔离与一致性,保障跨平台编译稳定性;若无法容器化,则统一CMake构建脚本与编译器版本,并规范编…

    2025年12月18日
    000
  • C++异常处理与信号处理区别解析

    C++异常处理用于程序内部同步错误,依赖堆栈展开和RAII确保资源安全;信号处理响应操作系统异步事件,适用于严重系统错误或外部中断,处理环境受限且不可抛出异常。两者层级不同,异常适合可恢复的逻辑错误,信号用于不可控的外部或致命问题。实际开发中,应通过volatile sig_atomic_t标志在信…

    2025年12月18日
    000
  • C++的虚函数表(vtable)是如何影响对象内存布局的

    C++虚函数表通过在对象中添加vptr指针影响内存布局,增加对象大小并调整成员变量偏移,vptr指向存储虚函数地址的vtable,实现多态调用;派生类覆盖或新增虚函数时更新对应vtable条目,多重继承可能引入多个vptr;静态成员变量存于静态区,不参与对象布局。 C++的虚函数表(vtable)通…

    2025年12月18日
    000
  • C++多重继承在C++中的实现方法

    C++多重继承通过内存布局和指针调整实现,派生类对象按声明顺序包含各基类子对象及自身成员,基类指针转换时编译器自动调整地址偏移;若基类含虚函数,派生类对象为每个带虚函数的基类子对象设置vptr指向对应vtable,调用虚函数时通过vptr定位函数并自动调整this指针指向完整对象;对于菱形继承,虚继…

    2025年12月18日
    000
  • c++如何将对象序列化_c++对象序列化与反序列化技术

    C++对象序列化方法包括手写函数、Boost.Serialization、JSON库(如nlohmann/json)和Protocol Buffers;选择依据性能、跨语言、开发效率等需求。 C++对象序列化,简单来说,就是把内存里的对象变成一串字节,方便存到文件里或者通过网络传输。反序列化就是反过…

    2025年12月18日
    000
  • C++如何正确使用数据类型

    正确使用C++数据类型需理解取值范围、内存占用和场景:优先选用int、long long等整型及float、double浮点型;推荐中int32_t、size_t等固定宽度类型保证跨平台一致性;避免有符号与无符号混合运算、浮点直接比较、未初始化变量等常见错误;结合auto、enum class提升安…

    2025年12月18日
    000
  • C++如何逐字符读取文件内容

    使用std::ifstream的get()函数可逐字符读取文件。需包含和头文件,打开文件后用file.get(ch)循环读取每个字符,直至EOF。该方法能处理空格、换行等所有字符,而>>操作符会跳过空白字符,不适合逐字符读取。读取前应检查文件是否成功打开,避免运行时错误。完整示例如下:包…

    2025年12月18日
    000
  • C++模板与SFINAE技巧使用方法

    SFINAE是C++模板元编程中通过替换失败来筛选重载函数的关键机制,常用于根据类型特征启用或禁用模板;结合enable_if可实现条件编译,但C++17的if constexpr和C++20的Concepts提供了更清晰、易维护的替代方案,在现代C++中应优先使用。 在C++中,模板是实现泛型编程…

    2025年12月18日
    000
  • C++如何在语法中处理数组和指针的关系

    数组名在表达式中常退化为指向首元素的指针,但数组本身具有固定大小和内存布局,而指针可重新赋值;函数参数中的数组实际以指针传递,无法通过sizeof获取长度,推荐使用std::array或std::vector以提升安全性和清晰度。 在C++中,数组和指针有着紧密的语法关联,但它们本质不同。理解它们的…

    2025年12月18日
    000
  • C++环境搭建完成后如何测试程序

    答案:搭建C++环境后,通过编译运行“Hello, World!”程序验证配置是否成功。具体步骤包括创建hello.cpp文件并写入标准输出代码,使用g++命令编译生成可执行文件,再在终端运行该程序;若输出“Hello, C++ World!”则表明环境配置正确。同时可通过g++ –ve…

    2025年12月18日
    000
  • C++模板特化与偏特化使用技巧

    模板特化与偏特化用于定制泛型实现,全特化针对特定类型完全重写模板,如 is_pointer;偏特化适用于类模板,可部分指定参数,如 is_same 或容器指针处理;函数模板仅支持全特化或重载;编译器优先选择最特化的版本,常用于 type traits、SFINAE 和元编程递归终止,提升性能与灵活性…

    2025年12月18日
    000
  • C++如何使用static修饰变量和函数

    静态成员变量属于类而非对象,所有实例共享同一份,需在类外定义初始化,可通过类名直接访问,生命周期贯穿程序运行期。 在C++中,static关键字用于修饰变量和函数时,主要影响其作用域、生命周期和链接性。根据使用场景不同,static的行为也有所区别。下面从类内和类外两个角度来说明如何使用static…

    2025年12月18日
    000
  • C++初学者如何编写小游戏井字棋

    井字棋可用二维字符数组表示棋盘,通过函数实现初始化、打印、玩家移动、胜负与平局判断,主循环控制游戏流程直至结束。 井字棋游戏对于C++初学者来说,是一个很好的练习项目,它能帮助你理解基本的控制流、数组和函数。关键在于拆解问题,一步步实现。 解决方案首先,我们需要一个棋盘,可以用二维数组表示。然后,我…

    2025年12月18日
    000
  • C++跨平台项目如何统一编译环境

    统一C++跨平台编译环境的核心是结合CMake与Docker:先用CMake抽象构建逻辑,生成各平台原生构建文件;再通过Docker封装操作系统、编译器和依赖库,确保编译环境一致。传统Makefile和IDE工程文件因依赖特定平台命令或工具链,难以跨平台复用。CMake通过“生成器”模式,将项目配置…

    2025年12月18日
    000
  • C++访问控制符public protected private使用规则

    答案:C++通过public、private、protected实现封装与继承控制。public成员构成外部接口,可被任意访问;private成员仅类内可见,保障数据安全与完整性;protected成员允许派生类访问,支持继承扩展但对外隐藏。默认情况下class为private,struct为pub…

    2025年12月18日
    000
  • C++结构体与模板结合使用方法

    将结构体与模板结合可实现泛型编程,提升代码复用性、类型安全和可维护性。通过定义template的结构体,如MyPair,可在编译时适配不同数据类型,避免重复代码。典型应用包括通用数据结构(如链表节点)、算法元素封装、策略模式及元信息描述。使用时需注意:模板定义应置于头文件、复杂错误提示可通过C++2…

    2025年12月18日
    000

发表回复

登录后才能评论
关注微信