0

0

C++如何抛出标准库异常类型

P粉602998670

P粉602998670

发布时间:2025-09-17 14:21:01

|

705人浏览过

|

来源于php中文网

原创

C++中抛出标准库异常需使用throw关键字并构造std::exception派生类对象,如std::invalid_argument或std::runtime_error,以提供清晰、统一的错误处理机制;优先选用标准异常类型可提升代码可读性、兼容性和维护性,避免自定义异常带来的复杂性;异常信息应具体、含上下文且具可操作性;通过RAII机制(如智能指针、文件流、锁对象)确保异常发生时资源正确释放,保障异常安全。

c++如何抛出标准库异常类型

在C++中,要抛出标准库异常类型,核心机制是使用

throw
关键字,并紧随其后一个
std::exception
或其派生类的对象。这样做的好处在于,它提供了一个统一且被广泛理解的错误处理接口,让程序在遇到非预期情况时能以结构化的方式中断当前流程,并通知调用者进行处理。这远比返回错误码要清晰和强大,尤其是在错误需要跨越多个函数调用层级传递时。

解决方案

抛出标准库异常,实际上就是创建一个

std::exception
家族中的某个类的实例,然后将其“扔出去”。这个家族涵盖了从逻辑错误到运行时错误的各种场景。最常见的方式是直接构造一个异常对象,并传入一个描述性的字符串。

以下是一些常用的标准库异常类型及其使用场景:

  • std::logic_error
    及其派生类: 这类异常通常表示程序内部的逻辑错误,是可以在程序设计阶段就避免的。

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

    • std::invalid_argument
      : 当函数接收到无效的参数时。
    • std::out_of_range
      : 当尝试访问容器或字符串中越界的元素时。
    • std::length_error
      : 当尝试创建一个过大的对象或容器时。
    • std::domain_error
      : 当输入参数超出了函数定义域时(例如数学函数)。
  • std::runtime_error
    及其派生类: 这类异常表示在程序运行时才能检测到的错误,通常是外部因素或不可预见的情况导致的。

    • std::overflow_error
      : 当算术运算结果溢出时。
    • std::underflow_error
      : 当算术运算结果下溢时。
    • std::range_error
      : 当结果超出有效范围,但未溢出时。
    • std::bad_alloc
      : 当内存分配失败时(由
      new
      抛出)。
    • std::bad_cast
      : 当
      dynamic_cast
      失败时。

抛出异常的语法很简单:

#include  // 包含大部分标准异常类型
#include 
#include 

// 模拟一个处理数据的函数
void process_data(int value) {
    if (value < 0) {
        // 抛出 invalid_argument 异常,因为参数不合法
        throw std::invalid_argument("process_data: Input value cannot be negative.");
    }

    if (value > 100) {
        // 抛出 out_of_range 异常,因为值超出了有效范围
        throw std::out_of_range("process_data: Value " + std::to_string(value) + " exceeds maximum limit of 100.");
    }

    // 假设在特定条件下会发生一个运行时错误
    if (value == 50) {
        throw std::runtime_error("process_data: A critical runtime error occurred during internal computation.");
    }

    std::cout << "Successfully processed value: " << value << std::endl;
}

int main() {
    // 示例1: 捕获 invalid_argument
    try {
        process_data(-10);
    } catch (const std::invalid_argument& e) {
        std::cerr << "Caught std::invalid_argument: " << e.what() << std::endl;
    }

    // 示例2: 捕获 out_of_range
    try {
        process_data(120);
    } catch (const std::out_of_range& e) {
        std::cerr << "Caught std::out_of_range: " << e.what() << std::endl;
    }

    // 示例3: 捕获 runtime_error
    try {
        process_data(50);
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught std::runtime_error: " << e.what() << std::endl;
    }

    // 示例4: 正常执行
    try {
        process_data(25);
    } catch (const std::exception& e) { // 使用基类捕获,可以捕获所有 std::exception 派生类
        std::cerr << "Caught generic std::exception for value 25: " << e.what() << std::endl;
    }

    // 示例5: 演示 bad_alloc (需要模拟内存耗尽)
    // 通常我们不会直接抛出 bad_alloc,它由 new 运算符在内存不足时自动抛出。
    // 但为了演示,我们可以手动抛出:
    try {
        // 假设这里尝试分配一个巨大的数组,并失败了
        // int* huge_array = new int[1000000000000ULL]; // 这会直接导致编译或运行时错误,不适合演示
        throw std::bad_alloc(); // 模拟内存分配失败
    } catch (const std::bad_alloc& e) {
        std::cerr << "Caught std::bad_alloc: " << e.what() << std::endl;
    }

    return 0;
}

在实际项目中,我们很少直接抛出

std::exception
基类,因为它太泛化了,无法提供具体的错误信息。通常会选择最能描述当前错误情况的派生类。

为什么C++开发应优先选择标准库异常类型而非自定义异常?

在我看来,优先选择标准库异常类型,是一个非常务实且有益的实践。这并不是说自定义异常一无是处,而是说在绝大多数情况下,标准库已经提供了足够丰富且语义清晰的错误类型。

首先,统一性和可预测性是最大的优势。当你的代码抛出

std::invalid_argument
std::runtime_error
时,任何一个有C++经验的开发者都能立刻理解其大致含义,并知道如何通过
e.what()
获取错误描述。如果每个模块都定义一套自己的自定义异常类,那么调用者就需要了解并捕获各种各样的自定义类型,这无疑增加了学习成本和代码的复杂性。试想一下,如果你要使用一个第三方库,它抛出的都是它自己定义的异常,你可能需要阅读大量文档才能知道如何正确处理这些错误。

其次,与标准库的兼容性。许多C++标准库函数本身就会抛出

std::exception
的派生类。例如,
std::vector
访问越界会抛出
std::out_of_range
new
失败会抛出
std::bad_alloc
。如果你的代码也遵循这一约定,那么整个程序的异常处理模型就会更加一致,也更容易与标准库的代码集成。

再者,避免过度设计。我发现很多时候,开发者创建自定义异常仅仅是为了给异常起一个更“贴切”的名字,或者为了在异常对象中携带一些额外的、其实通过错误信息字符串就能表达的数据。除非你的异常需要携带复杂的、结构化的、且无法通过

what()
字符串有效表达的数据(比如一个错误码枚举、一个文件路径列表、一个数据库连接状态等),否则自定义异常往往是画蛇添足。如果确实需要额外数据,可以考虑从
std::runtime_error
std::logic_error
派生,并添加成员变量,这样仍然能保持与标准异常体系的兼容性。

最后,减少重复造轮子。标准库异常体系已经相当完善,覆盖了大多数常见的错误场景。与其花时间设计、实现和维护一套自己的异常体系,不如直接利用现有成熟且经过充分测试的机制。这不仅节省了开发时间,也降低了引入新bug的风险。

Python v2.4 中文手册 chm
Python v2.4 中文手册 chm

Python v2.4版chm格式的中文手册,内容丰富全面,不但是一本手册,你完全可以把她作为一本Python的入门教程,教你如何使用Python解释器、流程控制、数据结构、模板、输入和输出、错误和异常、类和标准库详解等方面的知识技巧。同时后附的手册可以方便你的查询。

下载

抛出异常时,错误信息该如何组织才更有效?

抛出异常时,错误信息(也就是传递给异常构造函数的字符串)的质量至关重要。它不仅是给开发者看的,很多时候也会直接呈现在最终用户面前(比如通过日志系统)。一个好的错误信息,能让你在调试时事半功倍,也能帮助用户理解问题。

我的经验是,有效的错误信息应该具备以下几个特点:

  • 清晰且具体: 避免模糊的描述,比如“发生错误”或者“操作失败”。这些信息没有任何价值。相反,应该明确指出哪里出了问题,以及为什么会出问题。例如,
    "Failed to open configuration file 'settings.json' because file not found."
    就比
    "File error."
    好得多。
  • 包含上下文信息: 错误信息应该告诉我们错误发生的“地点”。比如,是哪个函数、哪个模块、哪个操作中出现了问题。如果可能,包含导致错误的具体数据或变量值。比如,
    "User ID '12345' not found in database during user profile lookup."
    提供了用户ID作为上下文。
  • 面向调用者,而非内部实现细节: 错误信息应该帮助调用者理解如何处理这个错误,或者至少知道问题出在哪里。避免暴露过多的内部实现细节,除非这些细节对于诊断问题至关重要。比如,
    "Database connection pool exhausted for server 'db.example.com'."
    "SQLSTATE 08006 connection_refused"
    更易懂。
  • 一致的格式和语言: 在整个项目中保持错误信息格式的一致性,例如总是以模块名或函数名开头。至于语言,如果项目是国际化的,通常会选择英文,因为英文在日志解析、工具链兼容性等方面有优势。如果面向的群体是单一语言,使用母语可能更直接。
  • 可操作性(如果可能): 理想情况下,错误信息甚至可以暗示如何解决问题。例如,
    "Insufficient permissions to write to directory '/var/log'. Please check file system permissions."
    这样的信息就非常有帮助。
// 错误信息示例
std::string filename = "non_existent.txt";
// 不好的错误信息
// throw std::runtime_error("File operation failed.");

// 好的错误信息
throw std::runtime_error("Failed to open file '" + filename + "' for writing. Check path and permissions.");

int index = 10;
std::vector data = {1, 2, 3};
// 不好的错误信息
// throw std::out_of_range("Index error.");

// 好的错误信息
throw std::out_of_range("Attempted to access vector at index " + std::to_string(index) + ", but vector size is " + std::to_string(data.size()) + ".");

组织好错误信息,能够显著提升代码的可维护性和调试效率。

异常安全与资源管理:抛出异常后如何确保资源不泄漏?

在C++中,当异常被抛出时,程序的控制流会发生非局部跳转,这意味着当前作用域内的局部变量可能无法正常执行到它们的析构函数。如果这些局部变量管理着资源(如内存、文件句柄、网络连接、锁等),那么这些资源就可能发生泄漏。这就是异常安全的核心挑战。

C++解决这个问题的黄金法则就是RAII(Resource Acquisition Is Initialization)。RAII的理念很简单:将资源的生命周期与对象的生命周期绑定。当对象被创建时(初始化时),它获取资源;当对象被销毁时(析构时),它释放资源。由于C++保证局部对象的析构函数在异常抛出时仍然会被调用(栈展开),因此只要资源被RAII对象管理,就能确保在异常发生时资源被正确释放,避免泄漏。

具体实践上:

  1. 智能指针管理动态内存:

    • std::unique_ptr
      用于独占所有权,当
      unique_ptr
      对象超出作用域时,它所指向的内存会被自动释放。
    • std::shared_ptr
      用于共享所有权,当最后一个
      shared_ptr
      销毁时,内存会被释放。
    • 避免裸指针管理
      new
      分配的内存。
      一旦在
      new
      delete
      之间抛出异常,
      delete
      就不会被调用。
    #include 
    #include 
    
    void risky_operation() {
        // 使用 unique_ptr 管理内存,即使抛出异常也能自动释放
        std::unique_ptr data = std::make_unique(100);
        // ... 其他操作,可能抛出异常 ...
        if (true /* 某个条件导致异常 */) {
            throw std::runtime_error("Something went wrong!");
        }
        // data 会在函数退出或异常抛出时自动析构,释放内存
    }
  2. 文件流自动关闭:

    • std::ifstream
      std::ofstream
      等文件流对象在析构时会自动关闭文件句柄。
    #include 
    #include 
    
    void process_file(const std::string& path) {
        std::ifstream file(path);
        if (!file.is_open()) {
            throw std::runtime_error("Could not open file: " + path);
        }
        // ... 读取或写入文件,可能抛出异常 ...
        // file 对象会在函数退出或异常抛出时自动析构,关闭文件
    }
  3. 锁管理:

    • std::lock_guard
      std::unique_lock
      等锁对象在析构时会自动释放互斥锁。
    #include 
    #include 
    
    std::mutex mtx;
    
    void access_shared_resource() {
        std::lock_guard lock(mtx); // 自动获取锁
        // ... 访问共享资源,可能抛出异常 ...
        if (true /* 某个条件导致异常 */) {
            throw std::runtime_error("Error during resource access!");
        }
        // lock 对象会在函数退出或异常抛出时自动析构,释放锁
    }
  4. 避免在析构函数中抛出异常:

    • 这是一个非常重要的规则。C++标准规定,如果在析构函数中抛出异常,并且这个析构函数是在另一个异常处理过程中被调用的(例如栈展开时),那么程序会立即终止(
      std::terminate
      )。这会导致程序行为不可预测。析构函数应该尽可能地不抛出异常。如果析构函数内部的操作可能失败,应该在内部处理掉这些错误,或者提供一个显式的
      close()
      release()
      方法让用户在对象销毁前调用。

通过始终遵循RAII原则,将所有资源包装在具有适当析构行为的对象中,我们可以构建出异常安全的代码,大大减少资源泄漏的风险。这是C++异常处理机制与语言特性相结合的强大体现。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

419

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

535

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

311

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

77

2025.09.10

resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

158

2023.12.20

scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

228

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

297

2023.10.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

319

2023.08.03

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

0

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.6万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号