C++常见的错误处理机制

目录

1.引言

2.异常处理(Exception Handling)

3.错误码(Error Codes)

4.断言(Assertions)

5.日志记录

6.条件检查和防御性编程

7.实际运用

8.总结


1.引言

        不管是谁,都不能保证自己写的程序没有bug。那么永远都抛不开的一个话题就是,离不开错误处理,尤其是生产型大型软件系统。应用软件系统运行属于循环处理事务,出错后需要保证不能让软件程序直接退出。这就需要使用一定的程序容错处理来应对。一般情况下,大型软件开发中的软件系统容错处理会结合异常处理、错误代码定义的使用与相应的出错处理日志记录,包括一定的参与大型生产系统的监控系统等配合保障系统的稳定性。C++也提供了多种错误处理机制,这些机制可以帮助开发者检测和响应程序运行中的各种错误情况。下面本章将会就C++软件系统中提供的异常处理作详细的讲述,包括基本概念以及应用操作情况。

2.异常处理(Exception Handling)

        软件应用程序中,异常处理机制是一种比较有效的处理系统运行时错误的方法。C++针对异常处理提供了一种标准的方法,用于处理软件程序运行时的错误,并用于处理软件系统中可预知或不可预知的问题。这样就可以保证软件系统运行的稳定性与健壮性。

        C++中异常的处理主要用于针对程序在运行时刻出现错误而提供的语言层面的保护机制。它允许开发者最大限度地发挥,针对异常处理进行相应的修改调整。

        C++应用程序中在考虑异常设计时,并不是所有的程序模块处理都需要附加上异常情况的处理。相对来说,异常处理没有普通方法函数调用速度快。过度的错误处理会影响应用程序运行的效率。通常在C++开发的软件系统中,应用程序都由对应的库、组件以及运行的具体不同模块组成。在设计时,异常的处理应充分考虑到独立程序库以及组件之间的情况。便于使用者在程序出现异常情况下,使用库或者组件的开发者能够快速定位出库、组件还是应用程序的错误。

        C++ 支持通过异常处理机制来捕获和处理错误。主要使用的关键字包括 trythrow 和 catch

  • try 块:用 try 关键字标识一个可能抛出异常的代码块。
  • throw 语句:用于抛出一个异常。可以抛出任何类型的对象,但通常是派生自 std::exception 的对象。
  • catch 块:用于捕获并处理异常。catch 块后面跟随的是异常类型和变量名,该变量存储了被捕获的异常对象。
#include <iostream>
#include <stdexcept>void func() {throw std::runtime_error("Something go wrong!");
}int main() {try {func();} catch (const std::runtime_error& e) {std::cerr << "Caught a runtime_error: " << e.what() << std::endl;} catch (...) {std::cerr << "Caught an unknown exception" << std::endl;}return 0;
}

自定义异常类

#pragma once
#include <stdexcept>class CJFixFormatException : public std::exception
{
public:using _Mybase = std::exception;explicit CJFixFormatException(const std::string& _Message) : _Mybase(_Message.c_str()) {}explicit CJFixFormatException(const char* _Message) : _Mybase(_Message) {}
};void func1() {throw CJFixFormatException("recv data less than J format template size");
}int main() {try {func1();} catch (const CJFixFormatException& e) {std::cerr << "Caught custom exception: " << e.what() << std::endl;}return 0;
}

异常的抛出和匹配原则

1.异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。

2.被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。

3.抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)

4.catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。

5.实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象, 使用基类捕获。

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

1.首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。

2.没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。

3.如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的 catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)捕获任意类型的异 常,否则当有异常没捕获,程序就会直接终止。

4.找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

3.错误码(Error Codes)

        使用返回码是另一种常见的错误处理方法,特别是在异常开销不合适的情况下。通过函数返回值指示错误,并在调用处检查这些返回值。错误码通常通过返回整数或枚举类型来表示不同的错误情况。

        示例如下:

#include <iostream>enum ErrorCode {Success = 0,FileNotFound,PermissionDenied,UnknownError
};ErrorCode openFile(const char* filename) {// Simulated file opening logicif (filename == nullptr) return FileNotFound;// More conditions could be checked herereturn Success;
}int main() {ErrorCode code = openFile(nullptr);if (code != Success) {std::cerr << "Error: " << code << std::endl;} else {std::cout << "File opened successfully" << std::endl;}return 0;
}

注意:错误码有一个问题,它只能定义可知的或者说可预知的。而对一些动态的、随机产生的异常或错误,它是无法正确返回的。或者说,当有一种错误码有N种情形可以产生时,就不好确定是哪种错误了。错误码一般不会有性能上的考虑,毕竟只是返回值的处理。使用错误码一定要有一个良好的习惯,对所有的错误码有一个非常好的注释或者有专门的说明文档,并且需要定时维护和更新。只有这样错误码才会起到应有的作用。

4.断言(Assertions)

C++之assert惯用法_c++ assert-CSDN博客

        断言用于在调试阶段捕捉不可恢复的错误。它在条件为假时终止程序执行并打印错误信息。适用于开发阶段的调试。

        示例如下:

#include <cassert>int divide(int a, int b) {assert(b != 0 && "Division by zero!");return a / b;
}int main() {int result = divide(10, 2);std::cout << "Result: " << result << std::endl;result = divide(10, 0); // This will trigger the assertion and terminate the programstd::cout << "Result: " << result << std::endl; // This line will not be executedreturn 0;
}

5.日志记录

spdlog一个非常好用的C++日志库(一): 简介与使用-CSDN博客

        日志记录是一种监控和记录错误发生情况的方法,常用于生产环境中进行故障排查。通过日志记录库(如 sdplog、log4cppBoost.Log 或标准库中的 <fstream>)记录程序的运行状态和错误信息,有助于调试和追踪问题。

        示例如下:

#include <iostream>
#include <fstream>
#include <stdexcept>void logError(const std::string& message) {std::ofstream logFile("error.log", std::ios_base::app);logFile << message << std::endl;
}void functionThatMightFail() {throw std::runtime_error("An error occurred");
}int main() {try {functionThatMightFail();} catch (const std::exception& e) {logError(e.what());std::cerr << "Caught exception: " << e.what() << std::endl;}return 0;
}

6.条件检查和防御性编程

浅谈C++中的防御性编程_c++防御性编程-CSDN博客

        通过条件检查在运行时防止错误发生,例如参数验证。

        示例如下:

#include <iostream>void processInput(int input) {if (input < 0) {std::cerr << "Error: input must be non-negative" << std::endl;return;}// 处理输入
}int main() {processInput(-1);processInput(10);return 0;
}

7.实际运用

        知晓了错误码与异常的控制后,如何进行选择呢?其实非常容易。那就是根据实际情况,能直接控制的,尽量选择错误码(包括在C++中使用枚举)。而对于一些影响到程序运行的稳定性和安全性的能随时可能产生的一般建议选择使用异常控制。
        在实际的开发过程中,为什么经常禁止使用异常。这里面有一个重要的原因,除了性能的问题外,一般来说,在C/C++中的严重的异常问题(包括动态生产的等),比如内存问题、指针问题等往往程序就直接Crash了。这时,再好的异常抛出来也没有什么用了。
        所以,异常的处理往往就这样尴尬了。但这并不代表异常没有用武之地,在一些明确有异常抛出的相关库的API中异常还是很有用处的。或者开发者自己封装相关的错误当做异常抛出,在某些情况下也会起到很好的控制作用。
        从目前实际看到的开源代码来看,异常更倾向在业务逻辑的上层应用,中下层一般以返回错误码居多。所以这也可以是开发者如何使用二者的一个大方向上的推荐,在底层,特别是接近于硬件的底层,建议使用错误码;在上层业务或UI上可以考虑使用异常,但个人建议要严格限制异常的使用。
        掌握这些大原则后,在实际应用中再根据实际情况,适当的进行个别的处理即可。还是那句话,合适的就是最好的。

8.总结

1.异常处理:用于捕获和处理运行时错误,适合处理不可预见的异常情况,而且性能开销较大。
2.错误返回码:适用于函数调用链中明确的错误检查,常用于嵌入式系统和性能敏感的代码,性能开销小。
3.断言:用于开发阶段捕捉编程错误,发布版本通常禁用。
4.日志记录:用于记录和追踪错误,帮助调试和维护。
5.条件检查和防御性编程:通过提前检查条件防止错误发生。

可以根据具体情况选择最合适的方法来确保程序的健壮性和可靠性。异常处理机制是现代 C++ 推荐的错误处理方式,而断言和错误码在特定情况下也非常有用。

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

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

相关文章

MyBatis-XML映射配置

引言 在 Mybatis 中&#xff0c;既可以通过注解配置 SQL 语句&#xff0c;也可以通过 XML 配置文件配置 SQL 语句。默认规则&#xff1a; 1. XML 映射文件的名称与 Mapper 接口名称一致&#xff0c;并且将 XML 映射文件和 Mapper 接口放置在相同包下 &#xff08;同包同名&…

MS9913N血糖测试 AFE 芯片

MS9913N 是一款血糖仪模拟前端 (AFE) 测量电路&#xff0c;内部集成 了 16bit 高性能 Σ-Δ ADC 、高精度运算放大器、低阻抗开关、高精 度基准电压产生电路和血糖测试 AC 信号调理电路&#xff0c;接口采用 I 2 C 通信协议&#xff0c;使外围器件更少&#xff0c;操…

C语言:-三子棋游戏代码:分支-循环-数组-函数集合

思路分析&#xff1a; 1、写菜单 2、菜单之后进入游戏的操作 3、写函数 实现游戏 3.1、初始化棋盘函数&#xff0c;使数组元素都为空格 3.2、打印棋盘 棋盘的大概样子 3.3、玩家出棋 3.3.1、限制玩家要下的坐标位置 3.3.2、判断玩家要下的位置是否由棋子 3.4、电脑出棋 3.4.1、…

【VS 调试WebApi —— localhost 及 ip访问】

VS 调试WebApi —— localhost 及 ip访问 1、localhost访问1.1 localhost端口修改 2、IP访问2.1 运行项目2.2 打开配置文件2.3 修改配置文件2.4 保存后重新运行 3、最终效果 1、localhost访问 VS运行项目时默认localhost访问。1.1 localhost端口修改 右键项目选中【属性】 --…

Flink链接Kafka

一、基于 Flink 的 Kafka 消息生产者 Kafka 生产者的创建与配置&#xff1a; 代码通过 FlinkKafkaProducer 创建 Kafka 生产者&#xff0c;用于向 Kafka 主题发送消息。Flink 执行环境的配置&#xff1a; 配置了 Flink 的检查点机制&#xff0c;确保消息的可靠性&#xff0c;支…

带头双向循环链表(数据结构初阶)

文章目录 双向链表链表的分类概念与结构实现双向链表定义链表结构链表打印判空申请结点初始化头插尾插头删尾删查找指定位置插入和删除销毁链表 顺序表和链表的分析结语 欢迎大家来到我的博客&#xff0c;给生活来点impetus&#xff01;&#xff01; 这一节我们学习双向链表&a…

41,【7】CTFHUB WEB SQL Cookie注入

搜索知Cookie 是由服务器发送给用户浏览器的一小段文本信息&#xff0c;浏览器会将其保存下来&#xff0c;并且在后续的请求中自动将 Cookie 发送回服务器。 所以可以联想到cookie会出现在请求处&#xff0c;可以使用BP抓包 找到了 又是整形注入 可知字节数为2 找到回显点 得…

unity学习16:unity里向量的计算,一些方法等

目录 1 unity里的向量&#xff1a; 2 向量加法 2.1 向量加法的几何意义 2.2向量加法的标量算法 3 向量减法 3.1 向量减法的几何意义 3.2 向量减法的标量算法 4 向量的标量乘法 5 向量之间的乘法要注意是左乘 还是右乘 5.1 注意区别 5.2 向量&#xff0c;矩阵&#x…

卷积神经05-GAN对抗神经网络

卷积神经05-GAN对抗神经网络 使用Python3.9CUDA11.8Pytorch实现一个CNN优化版的对抗神经网络 简单的GAN图片生成 CNN优化后的图片生成 优化模型代码对比 0-核心逻辑脉络 1&#xff09;Anacanda使用CUDAPytorch2&#xff09;使用本地MNIST进行手写图片训练3&#xff09;…

堆的实现【C++】

堆的实现 概念实现完整代码 概念 介绍堆之前得说一下二叉树&#xff0c;因为堆的逻辑结构是二叉树&#xff0c;二叉树的树的子集&#xff0c;树只有一个根节点&#xff0c;向下衍生出了很多节点&#xff0c;并且这个节点之间相互没有连接&#xff0c;除非是父子节点&#xff0…

UI自动化测试:异常截图和page_source

自动化测试过程中&#xff0c;是否遇到过脚本执行中途出错却不知道原因的情况&#xff1f;测试人员面临的不仅是问题的复现&#xff0c;还有对错误的快速定位和分析。而异常截图与页面源码&#xff08;Page Source&#xff09;的结合&#xff0c;正是解决这一难题的利器。 在实…

ASP.NET Core - 依赖注入(三)

ASP.NET Core - 依赖注入&#xff08;三&#xff09; 4. 容器中的服务创建与释放 4. 容器中的服务创建与释放 我们使用了 IoC 容器之后&#xff0c;服务实例的创建和销毁的工作就交给了容器去处理&#xff0c;前面也讲到了服务的生命周期&#xff0c;那三种生命周期中对象的创…

gitlab runner正常连接 提示 作业挂起中,等待进入队列 解决办法

方案1 作业挂起中,等待进入队列 重启gitlab-runner gitlab-runner stop gitlab-runner start gitlab-runner run方案2 启动 gitlab-runner 服务 gitlab-runner start成功启动如下 [rootdocserver home]# gitlab-runner start Runtime platform …

麦田物语学习笔记:构建游戏的时间系统

基本流程 1.代码思路 (1)新建一个TimeManager.cs (2)创建枚举变量来表示四季,在TimeManager里需要的变量有: 游戏内的秒,分钟,小时,天,月,年;游戏内的季节;控制一个季节有多少个月;控制时间的暂停;计时器tikTime (3)在Settings里添加计时器的阈值,以及各个时间的进位 (4)初始化…

Spring Boot教程之五十七:在 Apache Kafka 上发布 JSON 消息

Spring Boot | 如何在 Apache Kafka 上发布 JSON 消息 Apache Kafka是一个发布-订阅消息系统。消息队列允许您在进程、应用程序和服务器之间发送消息。在本文中&#xff0c;我们将了解如何在 Spring Boot 应用程序中向 Apache Kafka 发送 JSON 消息。 为了了解如何创建 Spring…

计算机网络 (44)电子邮件

一、概述 电子邮件&#xff08;Electronic Mail&#xff0c;简称E-mail&#xff09;是因特网上最早流行的应用之一&#xff0c;并且至今仍然是因特网上最重要、最实用的应用之一。它利用计算机技术和互联网&#xff0c;实现了信息的快速、便捷传递。与传统的邮政系统相比&#…

代码随想录算法训练营day02| 977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II

977. 有序数组的平方 双指针&#xff0c;新数组用k&#xff1b; 由于已经排序&#xff0c;所以比较两侧数据即可&#xff1b; class Solution { public:vector<int> sortedSquares(vector<int>& nums) {vector<int> ans(nums.size());int k nums.siz…

Unity 语音转文字 Vosk 离线库

市场有很多语音库&#xff0c;这里介绍Vosk SDK 除了支持untiy外还有原生开发服务器等 目录 安装unity示例demo下载语音训练文件运行demo结尾一键三联 注意事项 有可能debug出来的文本是空的&#xff0c;&#xff08;确保麦克风正常&#xff0c;且索引正确&#xff09;分大…

网络网络层ICMP协议

网络网络层ICMP协议 1. ICMP 协议介绍 ICMP&#xff08;Internet Control Message Protocol&#xff09;是 TCP/IP 协议簇中的网络层控制报文协议。用于在 IP 主机、路由器之间传递控制消息&#xff0c;提供可能有关通信问题的反馈信息。 以及用于网络诊断或调试&#xff08;…

Lianwei 安全周报|2025.1.13

新的一周又开始了&#xff0c;以下是本周「Lianwei周报」&#xff0c;我们总结推荐了本周的政策/标准/指南最新动态、热点资讯和安全事件&#xff0c;保证大家不错过本周的每一个重点&#xff01; 政策/标准/指南最新动态 01 美国国土安全部发布《公共部门生成式人工智能部署手…