[C++#33][异常] 错误码 | 抛出与捕获 | 异常安全 | 异常体系

news2024/9/21 16:41:11

目录

C语言与C++错误处理方式的对比及应用

一、C语言传统的错误处理方式

1. 终止程序:assert

2. 返回错误码

缺点:

二、C++中的异常处理机制

1. 基本概念

2. 异常的抛出与捕获

3. 异常的重新抛出

三、C++中的异常安全

1. 构造函数与析构函数的异常

2. RAII(资源获取即初始化)

3.使用

3.异常的使用

四、C++异常体系的优缺点

优点:

缺点:

五、实际应用

1.自定义异常体系

⭕1. Exception 基类

2. SqlException 子类

3. CacheException 子类

4. HttpServerException 子类

5. 模拟抛出异常的函数

SQLMgr()

CacheMgr()

HttpServer()

6. 异常处理的主函数 main()

7. 程序行为

⭕ 总结

2.C++标准库的异常体系

六. 总结


C语言与C++错误处理方式的对比及应用

在编程中,错误处理是不可避免的。传统的C语言和现代的C++在处理错误上有着明显的区别,前者依赖返回错误码的方式,而后者则引入了更为灵活的异常机制。这篇文章将探讨这两种语言在错误处理方面的不同,并介绍如何在实际工程中合理使用它们。

一、C语言传统的错误处理方式

1. 终止程序:assert

C语言中最简单的错误处理方式之一是直接终止程序,例如使用 assert。当程序在运行时遇到不可恢复的错误(如除零、内存访问越界等),程序会被强制终止。这种方式虽然简单,但有明显的缺陷:

  • 用户体验不佳:一旦程序遇到问题就立即崩溃,用户无法继续使用程序,特别是在遇到小问题时。
  • 缺乏灵活性:程序员无法提供其他的错误处理路径,例如记录日志、尝试恢复等。
2. 返回错误码

另一种常用的处理方式是返回错误码。很多C语言的库都会通过返回一个整数值来表示函数的执行结果。具体的错误信息通常会被存储在全局变量 errno 中,程序员可以通过查阅 errno 的值来判断出错的具体原因。

示例代码:

int func() {
    if (/* 错误发生 */) {
        errno = EINVAL;  // 设置错误码
        return -1;       // 返回错误
    }
    return 0;           // 返回成功
}
缺点:
  • 额外的错误处理负担:程序员需要手动检查每个函数调用的返回值,并根据 errno 做出相应的处理。这增加了代码的复杂性和出错的可能性。
  • 函数调用链复杂:如果错误在深层函数中发生,那么需要逐层返回错误,直到外层函数捕捉并处理,这导致代码难以维护。

例如:

  • 下面这段伪代码我们可以看到ConnnectSql中出错了,先返回给ServerStart,ServerStart再返回给main函数,main函数再针对问题处理具体的错误。
  • 如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因为抛出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{
	// 用户名密码错误
	if (...)
		return 1;

	// 权限不足
	if (...)
		return 2;
}

int ServerStart() {
	if (int ret = ConnnectSql() < 0)
		return ret;
	int fd = socket()
		if(fd < 0)
		return errno;
}

int main()
{
	if (ServerStart() < 0)
		...

		return 0;
}

二、C++中的异常处理机制

C++ 引入了异常处理机制,使得程序在遇到错误时不需要直接终止或通过返回值处理,可以通过抛出异常的方式将问题传递到合适的地方进行处理。

1. 基本概念
  • throw:用于在错误发生时抛出异常。异常可以是任何类型的对象,程序员可以抛出字符串、整型或自定义对象来传递错误信息。
  • try包含可能抛出异常的代码块。如果 try 块中的代码抛出了异常,程序会跳转到相应的 catch 块处理异常。
  • catch:用于捕获异常。catch 块可以捕获特定类型的异常,并根据异常类型进行处理。

示例代码:

double Division(int a, int b) {
    if (b == 0)
        throw "Division by zero condition!";  // 抛出异常
    return (double)a / (double)b;
}

int main() {
    try {
        cout << Division(10, 0) << endl;  // 可能抛出异常
    } catch (const char* errmsg) {
        cout << errmsg << endl;  // 捕获并处理异常
    }
    return 0;
}

2. 异常的抛出与捕获

C++的异常处理机制基于类型匹配。在抛出异常时,程序会根据异常的类型查找最接近的 catch。如果没有匹配的 catch,程序将继续沿调用链向外查找,最终未被捕获的异常会导致程序终止。

  • 类型匹配:抛出的异常必须与 catch 块的类型匹配,否则将无法捕获。
  • 继承与多态:可以抛出派生类对象,并使用基类捕获。这在复杂项目中非常实用,因为可以通过捕获基类来处理一类相关的错误。
catch (...) { 
    // 捕获所有类型的异常,防止程序崩溃
    cout << "Unknown exception occurred!" << endl;
}
  • 抛异常可以抛任意类型对象
  • 捕获时,要求类型匹配

3. 异常的重新抛出

有时,捕获到异常后,当前函数无法处理该异常,而需要将其传递给更高层的函数来处理。这时,可以通过 throw 关键字重新抛出异常。

catch (...) {
    // 做一些处理,例如释放资源
    throw;  // 重新抛出异常
}

三、C++中的异常安全

异常处理虽然强大,但在C++中引入了新的风险,特别是资源泄漏问题。异常抛出后,如果没有正确释放资源(如内存、文件句柄等),可能导致程序资源泄漏。

1. 构造函数与析构函数的异常
  • 构造函数:如果构造函数抛出异常,可能导致对象没有被完全构造,进而出现内存泄漏等问题。
  • 析构函数:最好不要在析构函数中抛出异常,析构函数的主要职责是清理资源,如果抛出异常,可能导致资源无法被正确释放。
2. RAII(资源获取即初始化)

C++使用RAII技术来确保资源在异常发生时也能被正确释放。通过智能指针等工具,程序员可以确保资源在超出作用域时自动被释放,避免了内存泄漏问题。

std::unique_ptr<int[]> array(new int[10]);
3.使用

3.异常的使用

我们先看看异常怎么用的,再说其他细节!

下面这段代码出现除0错误程序就会终止。但我不想让程序终止,因此当被除数为0就抛一个异常,很简单就是用throw后面加一个对象,可以是任意类型对象如int、string等等。后面catch对异常进行捕获。

double Division(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
        throw "Division by zero condition!";
    else
        return ((double)a / (double)b);
}
void Func()
{
    int len, time;
    cin >> len >> time;
    cout << Division(len, time) << endl;
}

int main()
{
    try {
        Func();
    }
    catch (const char* errmsg)
        {
            cout << errmsg << endl;
        }
    return 0;
}

测试:

逐语句调试,感受一下捕获的过程:


四、C++异常体系的优缺点

优点:
  1. 清晰的错误信息:通过异常对象,程序员可以详细描述错误信息,便于调试。
  2. 简化代码逻辑:相比返回错误码的方式,异常处理可以自动沿调用链传播错误,减少显式的错误处理逻辑。
  3. 支持多态:通过抛出派生类异常,捕获基类异常,实现统一的异常处理。
缺点:
  1. 程序执行流混乱:异常使得程序的执行流不再线性,导致调试困难。
  2. 性能开销:尽管现代硬件的处理速度很快,但异常的捕获与栈展开仍有一定的性能影响。
  3. 容易导致资源泄漏没有正确处理异常时,资源(如内存、文件句柄)可能无法被释放,导致泄漏。

五、实际应用

1.自定义异常体系
  • 实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了
  • 所以实际中都会定义一套继承的规范体系。 这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了

下面代码展示了一个服务器开发中常用的异常继承体系,并模拟了数据库、缓存、HTTP服务器中的错误处理方式。通过继承 Exception 类,定义了不同类型的异常类,模拟了抛出和捕获异常的过程。让我们逐步解释代码中的各个部分。

⭕1. Exception 基类

class Exception
{
public:
    Exception(const string &errmsg, int id)
        : _errmsg(errmsg), _id(id)
        {}

    virtual string what() const//捕获后的处理
    {
        return _errmsg;
    }
protected:
    string _errmsg;  // 错误信息
    int _id;         // 错误编号
};
  • Exception 是一个基类,代表通用的异常。它有两个成员变量:
    • _errmsg:表示错误信息。
    • _id:表示错误的编号(可以用作错误分类或错误码)。
  • what() 函数是一个虚函数,用于返回错误信息。子类可以重写这个方法,提供更多的上下文信息。

2. SqlException 子类

class SqlException : public Exception
{
public:
    SqlException(const string &errmsg, int id, const string &sql)
        : Exception(errmsg, id), _sql(sql)
        {}

    virtual string what() const
    {
        string str = "SqlException:";
        str += _errmsg;
        str += "->";
        str += _sql;
        return str;
    }
private:
    const string _sql;  // SQL查询语句
};
  • SqlExceptionException 的派生类,专门用于处理数据库相关的异常。
  • 它有一个额外的成员变量 _sql,用于存储触发异常的 SQL 查询语句。
  • 它重写了 what() 方法,提供了更详细的错误信息,包含了 SQL 查询语句,用于调试和定位问题。

3. CacheException 子类

class CacheException : public Exception
{
public:
    CacheException(const string &errmsg, int id)
        : Exception(errmsg, id)
        {}

    virtual string what() const
    {
        string str = "CacheException:";
        str += _errmsg;
        return str;
    }
};
  • CacheException 也是从 Exception 继承而来,表示缓存相关的异常。
  • 它没有额外的成员变量,只重写了 what() 方法,返回了缓存错误的标识 CacheException: 和相应的错误信息。

4. HttpServerException 子类

class HttpServerException : public Exception
{
public:
    HttpServerException(const string &errmsg, int id, const string &type)
        : Exception(errmsg, id), _type(type)
        {}
    
    virtual string what() const
    {
        string str = "HttpServerException:";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }
private:
    const string _type;  // HTTP请求类型 (如 "GET", "POST")
};
  • HttpServerException 是另一个从 Exception 派生的类,处理 HTTP 服务器相关的异常。
  • 它多了一个 _type 成员变量,表示 HTTP 请求的类型(如 GETPOST)。
  • 它同样重写了 what() 方法,返回更具体的 HTTP 相关错误信息。

5. 模拟抛出异常的函数

SQLMgr()
void SQLMgr()
{
    srand(time(0));
    if (rand() % 7 == 0)
    {
        throw SqlException("权限不足", 100, "select * from name = '张三'");
    }
}
  • SQLMgr 模拟了数据库操作,在某些情况下会抛出 SqlException,表示数据库权限不足的异常,并附带了 SQL 查询语句。
CacheMgr()
void CacheMgr()
{
    srand(time(0));
    if (rand() % 5 == 0)
    {
        throw CacheException("权限不足", 100);
    }
    else if (rand() % 6 == 0)
    {
        throw CacheException("数据不存在", 101);
    }
    SQLMgr();
}
  • CacheMgr 模拟了缓存管理操作,它可能会抛出两种不同的 CacheException:权限不足或数据不存在。
  • 如果缓存没有抛出异常,它还会调用 SQLMgr,这可能会进一步抛出 SqlException
HttpServer()
void HttpServer()
{
    srand(time(0));
    if (rand() % 3 == 0)
    {
        throw HttpServerException("请求资源不存在", 100, "get");
    }
    else if (rand() % 4 == 0)
    {
        throw HttpServerException("权限不足", 101, "post");
    }
    CacheMgr();
}
  • HttpServer 模拟了 HTTP 请求处理,可能抛出 HttpServerException,表示请求资源不存在或权限不足。
  • 如果没有发生 HTTP 异常,它会调用 CacheMgr,因此可能抛出缓存或数据库相关的异常。

6. 异常处理的主函数 main()

int main()
{
    while (1)
    {
        try
        {
            HttpServer();  // 可能抛出多种异常
        }
        catch (const Exception& e) // 捕获基类的异常
        {
            cout << e.what() << endl;  // 多态调用,输出具体的异常信息
        }
        catch (...)
        {
            cout << "Unkown Exception" << endl;  // 捕获所有未明确处理的异常
        }
    }
    return 0;
}
  • main() 函数中,程序通过 try-catch 块捕获所有从 HttpServer() 抛出的异常。
  • 使用基类 Exception 的引用捕获所有派生类异常,并通过多态机制调用派生类的 what() 方法输出具体的错误信息。
  • catch(...) 捕获所有没有明确类型的异常,确保即使抛出了未知类型的异常,程序也不会崩溃。

7. 程序行为

在每次循环中,程序随机抛出不同的异常,如:

  • SQL权限不足 (SqlException)
  • 缓存数据不存在 (CacheException)
  • HTTP请求资源不存在 (HttpServerException)

这些异常会被捕获,并根据异常类型输出相应的错误信息。程序不会因为异常而崩溃,因为有全面的异常捕获机制。

运行:

会随机捕获异常~

⭕ 总结
  • 继承与多态派生类如 SqlExceptionCacheExceptionHttpServerException 通过继承 Exception 基类实现了多态。即在捕获基类异常时,能够正确识别并调用派生类的 what() 方法。
  • 异常处理结构化:通过不同的异常类型,开发者可以根据问题的类别和严重程度灵活处理不同模块的错误,比如数据库、缓存和HTTP服务器。
  • 防御性编程:通过 catch(...) 捕获未识别的异常,确保程序不会因未捕获的异常导致崩溃,从而提高了程序的稳定性。

这是一个常见的异常处理体系,在服务器开发和大型系统中尤为重要。


2.C++标准库的异常体系

C++提供了一系列标准的异常,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的

说明:

  • 实际中我们可以去继承exception类实现自己的异常类
  • 但是实际中很多公司像上面一 样自己定义一套异常继承体系。因为C++标准库设计的不够好用

六. 总结

无论是C语言的返回错误码还是C++的异常机制,错误处理都是程序开发中的重要组成部分。C语言适合处理简单错误,而C++的异常处理则为复杂项目提供了更多的灵活性。合理使用这两种语言的错误处理方式,可以提高程序的健壮性和可维护性。

  1. C语言中,优先使用返回错误码:对于简单的错误,C语言通过返回值来处理是合理的。需要注意检查每个函数调用的返回值,确保及时处理错误。
  2. C++中,建议使用异常处理:对于复杂的应用,C++的异常机制更为合适,尤其是在处理构造函数等无法返回错误码的场景下。
  3. 遵循异常安全原则:在异常可能引发的资源管理问题上,使用RAII(如智能指针)来确保资源的正确释放。
  4. 自定义异常体系:在大型项目中,建议自定义异常类,并通过继承实现不同模块的异常管理。捕获基类异常可以简化代码,并提高异常处理的灵活性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2113967.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字图像噪声常用的概率分布

高斯、瑞利、指数、埃尔朗分布都是指数家族分布。 注&#xff1a;冈萨雷斯的四版都是错的。 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码

KDD 2024 时空数据(Spatio-temporal) ADS论文总结

2024 KDD&#xff08; ACM SIGKDD Conference on Knowledge Discovery and Data Mining, 知识发现和数据挖掘会议&#xff09;在2024年8月25日-29日在西班牙巴塞罗那举行。 本文总结了KDD2024有关时空数据(Spatial-temporal) 的相关论文&#xff0c;如有疏漏&#xff0c;欢迎大…

基于深度学习的遥感图像分类识别系统,使用PyTorch框架实现

取5个场景 [海滩, 灌木丛, 沙漠, 森林, 草地] 划分数据集 train&#xff1a;val&#xff1a;test 7&#xff1a;2&#xff1a;1 环境依赖 pytorch1.1 or 1.0 tensorboard1.8 tensorboardX pillow 注意调低batch_size参数特别是像我这样的渣渣显卡 使用方法 只需要指…

MCU4.逻辑门电路的符号

1.与运算 C语言符号:&(按位与)和&&(逻辑与) 逻辑门电路的符号: 2.或运算 符号:|(按位或)和||(逻辑或) 逻辑门电路的符号: 3.非运算 C语言符号:!(按位非) 逻辑门电路的符号: 4.同或运算 相同为真(0⊙01,1⊙11),否则为假(0⊙10,1⊙00) 符号:⊙(按位同或) 图…

网络学习-eNSP配置ACL

AR1路由器配置 <Huawei>system-view Enter system view, return user view with CtrlZ. [Huawei]undo info-center enable Info: Information center is disabled. [Huawei]interface gigabitethernet 0/0/0 [Huawei-GigabitEthernet0/0/0]ip address 192.168.2.254 24 …

头脑风暴必备:四款在线思维导图工具详解

在快节奏的现代生活中&#xff0c;工作和学习常常需要我们去挖掘新的思维与灵感&#xff1b;在这个过程中&#xff0c;在线思维导图工具无疑是我们的重要伙伴&#xff1b;今天&#xff0c;我们将详细介绍四款在工作和学习中常用的在线思维导图工具给大家&#xff01;&#xff0…

网络安全(sql注入)

这里写目录标题 一. information_schema.tables 和 information_schema.schemata是information_schema数据库中的两张表1. information_schema.schemata2. information_schema.tables 二. 判断注入类型1. 判断数字型还是字符型注入2. 判断注入闭合是""还是 三. 判断表…

数据结构(邓俊辉)学习笔记】排序 5——选取:通用算法

文章目录 1. 尝试2. quickSelect3.linearSelect&#xff1a;算法4. linearSelect&#xff1a;性能分析5. linearSelect&#xff1a;性能分析B6. linearSelect&#xff1a;性能分析C 1. 尝试 在讨论过众数以及特殊情况下中位数的计算方法以后&#xff0c;接下来针对一般性的选取…

「大数据分析」图形可视化,如何选择大数据可视化图形?

​图形可视化技术&#xff0c;在大数据分析中&#xff0c;是一个非常重要的关键部分。我们前期通过数据获取&#xff0c;数据处理&#xff0c;数据分析&#xff0c;得出结果&#xff0c;这些过程都是比较抽象的。如果是非数据分析专业人员&#xff0c;很难清楚我们这些工作&…

【网络安全】服务基础第二阶段——第三节:Linux系统管理基础----Linux用户与组管理

目录 一、用户与组管理命令 1.1 用户分类与UID范围 1.2 用户管理命令 1.2.1 useradd 1.2.2 groupadd 1.2.3 usermod 1.2.4 userdel 1.3 组管理命令 1.3.1 groupdel 1.3.2 查看密码文件 /etc/shadow 1.3.4 passwd 1.4 Linux密码暴力破解 二、权限管理 2.1 文件与目…

RISC-V (十)任务同步和锁

并发与同步 并发&#xff1a;指多个控制流同时执行。 多处理器多任务。一般在多处理器架构下内存是共享的。 单处理器多任务&#xff0c;通过调度器&#xff0c;一会调度这个任务&#xff0c;一会调度下个任务。 共享一个处 理器一个内存。…

C语言指针详解-包过系列(一)目录版

C语言指针详解-包过系列&#xff08;一&#xff09;目录版 1.内存和地址1.1内存1.2 深入理解编址 2.指针变量和地址2.1 取地址操作符&#xff08;&&#xff09;2.2 指针变量和解引用操作符&#xff08;*&#xff09;2.2.1 指针变量2.2.2 指针变量各部分理解2.2.3 解引用操作…

重启顺风车的背后,是高德难掩的“野心”

以史鉴今&#xff0c;我们往往可以从今天的事情中&#xff0c;看到古人的智慧&#xff0c;也看到时代的进步。就如西汉后期文学家恒宽曾说的&#xff0c;“明者因时而变&#xff0c;知者随事而制”。 图源来自高德官方 近日&#xff0c;高德就展现了这样的智慧。在网约车市场陷…

团队比赛活动如何记分?

团队比赛时如何记分&#xff1f; 在当今快节奏的社会中&#xff0c;团队合作和竞争已成为推动个人和集体发展的重要方式。无论是在学校的体育赛事、公司的团建活动&#xff0c;还是社区的娱乐竞赛中&#xff0c;团队比赛都扮演着不可或缺的角色。然而&#xff0c;组织一场成功的…

ubuntu 安装配置 ollama ,添加open-webui

ubuntu 安装配置 ollama 下载安装 [https://ollama.com/download](https://ollama.com/download)一 安装方法 1 命令行下载安装一 安装方法 2 , 手动下载安装 二 配置模型下载路径三 运行1 启动 ollama 服务2 运行大模型 四 添加开机自启服务 ollama serve1 关闭 ollama 服务2 …

2158. 直播获奖(live)

代码 #include<bits/stdc.h> using namespace std; int main() {int n,w,a[100000],cnt[601]{0},i,j,s;cin>>n>>w;for(i0;i<n;i){scanf("%d",&a[i]);cnt[a[i]];int x(i1)*w/100;if(!x) x1;for(j600,s0;j>0;j--){scnt[j];if(s>x){cou…

离线语音通断器

很长一段时间没有更新了 这段时间丰富了离线语音通断器的款式&#xff0c;目前成熟的模块包括单路&#xff0c;三路&#xff0c;四路&#xff0c;八路&#xff0c;输出支持无源输出&#xff0c;有源输出&#xff0c;目前主要针对房车改装市场做一些定制化的客户&#xff0c;同…

快排Java

快速排序的复杂度 快排代码 package leetcode;import java.util.Arrays;public class QuickSort {public static void quickSort(int[] array, int low, int high) {if (low < high) {int pivotIndex partition(array, low, high);quickSort(array, low, pivotIndex - 1);…

winserver2012 关闭iis

1&#xff0c;打开控制面板&#xff0c;搜索管理工具 2&#xff0c;打开管理工具 3&#xff0c;双击打开&#xff1a; 3.1&#xff0c;看到服务列表&#xff1a;右键管理网站-浏览&#xff0c;可以查看web服务&#xff1b; 4&#xff0c;如何关闭&#xff1a;右键结束即可。

【Linux】探索进程优先级的奥秘,解锁进程的调度与切换

目录 进程优先级&#xff1a; 是什么&#xff1f; 为什么存在进程优先级的概念呢&#xff1f; Linux为什么调整优先级是要受限制的&#xff1f; PRI vs NICE Linux的调度与切换 概念准备&#xff1a; 那我们到底怎样完成进程的调度和切换呢&#xff1f; 区分&#xff…