c++异常

c++异常

  • 1. c++异常概念
  • 2. 异常的简单使用
  • 3. 自定义异常体系
  • 4. 异常的规范和安全
    • 4.1 异常规范
    • 4.2 异常安全
  • 5. C++标准库的异常体系和异常的优缺点

1. c++异常概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。 C++ 异常处理涉及到三个关键字:try、catch 和 throw。

  1. throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  2. catch: 在想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  3. try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。

使用 try/catch 语句的语法如下所示:

try{
    // 保护代码
}
catch (ExceptionName e1) {
    // catch 块
}
catch (ExceptionName e2) {
    // catch 块
}
catch (ExceptionName eN) {
    // catch 块
}

如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。

抛出异常时,可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。

2. 异常的简单使用

异常的抛出和匹配原则:

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
  4. catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么。
  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用。

捕获异常的例子:

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

捕获异常时,catch 块跟在 try 块后面,用于捕获异常。你可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。例如:

try 
{
    // 保护代码
} 
catch (ExceptionName e) 
{
    // 处理 ExceptionName 异常的代码
}

下面是一个实例,抛出一个除以零的异常,并在 catch 块中捕获该异常:

#include <iostream>
using namespace std;

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

int main() 
{
    int x = 50;
    int y = 0;
    double z = 0;
    try 
    {
        z = division(x, y);
        cout << z << endl;
    } 
    catch (const char* msg) 
    {
        cerr << msg << endl;
    }
    return 0;
}

于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时,它会产生下列结果:Division by zero condition!

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

  1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
  2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
  3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

栈展开过程如下,首先检查throm本身 是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则处理。没有则退出 当前函数栈,继续在调用函数的栈中进行查找,不断;重复上述过程,若到达main函数的栈,依旧没有匹配的,则终止程序

#include <iostream>
using namespace std;

class Exception
{
public:
	Exception(const int code, const string msg)
		:_code(code)
		,_msg(msg)
	{}
	int GetCode() const
	{
		return _code;
	}
	string Getmsg() const
	{
		return _msg;
	}
	~Exception()
	{}
private:
	int _code; // 错误码
	string _msg; // 错误信息
};
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		Exception e(1, "Division by zero condition!");
		throw(e);
	}
	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 Exception& e)
	{
		cout << "错误码:" << e.GetCode() << ",错误信息:" << e.Getmsg() << endl;
	}
	catch (...)
	{
		cout << "unkown exception" << endl;
	}
	return 0;
}

3. 自定义异常体系

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

class MyException : public std::exception 
{
public:
    MyException(const std::string& msg)
        : message(msg)
    {}

    const char* what() const noexcept override
    {
        return message.c_str();
    }
private:
    std::string message;
};

class FileReadException : public MyException 
{
public:
    FileReadException(const std::string& filename)
    : MyException("Error reading file: " + filename) 
    {}
};

class NetworkException : public MyException 
{
public:
    NetworkException(const std::string& host)
    : MyException("Network error with host: " + host) 
    {}
};

void readFromFile(const std::string& filename) 
{
    throw FileReadException(filename);
}

void connectToServer(const std::string& host) 
{
    throw NetworkException(host);
}

int main() 
{
    try 
    {
        readFromFile("data.txt");
    }
    catch (const MyException& e) 
    {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }
    return 0;
}

首先,我们可以创建一个自定义的基本异常类,例如 MyException,继承自标准库的 std::exception,在基本异常类的基础上,我们可以派生出其他业务类型的异常,例如 FileReadException 和 NetworkException。

4. 异常的规范和安全

4.1 异常规范

C++ 异常规范是一项 C++ 语言功能,用于指示程序员对函数可能抛出的异常类型的意图。它允许我们明确声明函数是否可以或不可以因异常而退出。编译器可以利用这些信息来优化对函数的调用,并在异常意外地离开函数时终止程序。

异常规范有两个关键字:
throw:当问题出现时,程序会抛出一个异常。我们使用 throw 关键字来实现这一点。
noexcept:在您想要处理问题的地方,通过异常处理程序捕获异常。noexcept 关键字用于指定函数不会抛出异常。
让我们来看一个简单的例子:

#include <iostream>

double divide(int a, int b) noexcept 
{
    if (b == 0) 
    {
        throw "Division by zero condition!";
    }
    return static_cast<double>(a) / b;
}

int main() 
{
    int numerator = 50;
    int denominator = 0;
    double result = 0;

    try 
    {
        result = divide(numerator, denominator);
        std::cout << "Result: " << result << std::endl;
    }
    catch (const char* msg) 
    {
        std::cerr << "Exception caught: " << msg << std::endl;
    }

    return 0;
}

在上面的代码中,我们定义了一个 divide 函数,它计算两个整数的除法。如果除数为零,我们抛出一个异常。在 main 函数中,我们使用 try 块来调用 divide 函数,并在 catch 块中捕获异常。注意,我们在 divide 函数的声明中使用了 noexcept 关键字,表示该函数不会抛出异常。
在这里插入图片描述
C++ 异常规范是一个有关异常处理的重要主题。它的优点和缺点如下。
优点:

  1. 清晰准确的错误信息:异常对象的抛出相比于错误码的方式,可以清晰准确地展示出错的各种信息,甚至还可以包含堆栈的信息,有助于定位程序的 Bug.
  2. 简化代码:异常减少了对错误码的层层传递和检查,使得正常代码的结构更清晰。每个错误码的处理不再需要一堆 if-else 语句,提高了代码可读性.
  3. 方便的构造函数报错:异常让构造函数更容易报告错误,而不需要返回错误码.

缺点:

  1. 历史原因:某些项目禁用异常,主要是因为历史原因。例如,Google 曾禁用异常,但这是因为他们已有大量非异常安全的代码,而不是出于哲学或道德原因
  2. 性能问题:在某些项目中,异常被禁用,因为工具链不能保证抛出异常时的实时性能。这可能导致二进制文件大小增加,因为异常产生的位置决定了需要如何做栈展开,这些数据需要存储在表里.
  3. 二进制文件大小:使用异常会增加二进制文件大小,因为异常产生的位置需要存储在表里。典型情况下,使用异常和不使用异常比,二进制文件大小会有约百分之十到二十的上升.

4.2 异常安全

C++ 异常安全是一项关键的编程概念,旨在确保程序在发生异常时能够维持一致的状态,避免资源泄漏和数据结构破坏。
异常安全的概念:异常安全意味着当程序在异常发生时,它可以“回退得很干净”。具体而言,一个函数在发生异常时应该满足两个条件:1.不泄漏资源:已申请的资源必须被正确释放。2.不破坏数据结构:不会导致野指针等问题。

异常安全分为三个级别:

  1. 基本级别:可能发生异常,但在异常发生时代码保证做了必要的清理工作,使对象保持合法状态。
  2. 强烈级别:可能发生异常,但在异常发生时代码保证对数据的任何修改都可以回滚,即要么完全成功,要么保持调用之前的状态。
  3. 无异常:函数不会抛出异常(例如标准库的 swap 函数)。

反面例子:资源泄漏:例如,互斥锁的获取和释放。如果在获取锁后发生异常,释放锁的代码将不会执行,导致资源泄漏。
数据破坏:例如,自定义类的赋值操作符重载。如果在分配新资源时抛出异常,对象的数据可能会遭到破坏。

解决方案:
资源泄漏:使用对象来管理资源,例如使用 RAII 技术或智能指针。
数据破坏:使用“拷贝并交换”策略,先创建副本,然后在副本上进行修改,最后交换资源。
总结:编写异常安全的代码需要注意资源管理和数据修改的问题,以及使用 RAII 和拷贝并交换等技术。

最佳实践:
使用 RAII 管理资源,避免资源泄漏。
注意异常发生时的回滚机制。
减少全局变量的使用,保证局部变量的异常安全性。
不知道如何处理异常时,不要捕获异常,直接终止程序。

5. C++标准库的异常体系和异常的优缺点

C++标准库定义了一套异常类体系,其根部是名为 exception 的抽象基类。标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception)。接下来深入了解一下这些异常类:

1.exception:
exception 是所有标准异常的基类。
它声明了一个 what() 虚函数,用于返回一个 const char*,表示被抛出异常的文字描述。

2.标准异常类:
这些异常类都继承自 exception,并提供了特定类型的错误信息。
一些常见的标准异常类包括:
logic_error 及其子类:表示逻辑错误,例如 std::invalid_argument、std::domain_error 等。
runtime_error 及其子类:表示运行时错误,例如 std::overflow_error、std::out_of_range 等。

3.使用标准异常:
在代码中,我们可以使用这些标准异常来处理特定的错误情况。
例如,如果发生了除以零的操作,可以抛出 std::runtime_error。

异常处理是软件开发中的重要主题,它有一些优点和缺点。

优点:

  1. 清晰准确的错误信息:异常对象的抛出相比于错误码的方式,可以清晰准确地展示出错的各种信息,甚至还可以包含堆栈的信息,有助于定位程序的 Bug。
  2. 简化代码:异常减少了对错误码的层层传递和检查,使得正常代码的结构更清晰。每个错误码的处理不再需要一堆 if-else 语句,提高了代码可读性。
  3. 方便的构造函数报错:异常让构造函数更容易报告错误,而不需要返回错误码。

缺点:

  1. 历史原因:某些项目禁用异常,主要是因为历史原因。例如,Google 曾禁用异常,但这是因为他们已有大量非异常安全的代码,而不是出于哲学或道德原因。
  2. 性能问题:
    在某些项目中,异常被禁用,因为工具链不能保证抛出异常时的实时性能。这可能导致二进制文件大小增加,因为异常产生的位置决定了需要如何做栈展开,这些数据需要存储在表里。
  3. 二进制文件大小:使用异常会增加二进制文件大小,因为异常产生的位置需要存储在表里。
    典型情况下,使用异常和不使用异常比,二进制文件大小会有约百分之十到二十的上升。

总之,异常处理在不同项目和场景中有不同的利弊。在编写代码时,需要权衡这些因素,根据项目需求和性能要求来选择是否使用异常。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【GCN】GCN学习笔记一

谱域图卷积 卷积 卷积定义离散空间的卷积 图卷积简介 卷积定理谱域图卷积实现思路如何定义图上的傅里叶变换拉普拉斯矩阵 &#xff08;Laplacian Matrix&#xff09;拉普拉斯矩阵的性质拉普拉斯矩阵的谱分解拉普拉斯矩阵与拉普拉斯算子 图傅里叶变换 图上的信号表示经典傅里叶变…

ubuntu20.04在docker下运行ros-noetic进行开发

经常折腾虚拟机各双系统 &#xff0c; 想着不如把docker利用起来&#xff0c;下面算是一个初学者使用docker运行ros的记录&#xff1a; 1. 安装 使用官方安装脚本自动安装 curl -fsSL https://test.docker.com -o test-docker.shsudo sh test-docker.sh验证是否安装成功 doc…

力扣 hot100 最长连续序列 哈希去重 双指针

128. 最长连续序列 ⭐ AC code class Solution {public int longestConsecutive(int[] nums) {if (nums.length 0)// 特判为空的数组&#xff0c;返回0return 0; // set实现去重HashSet<Integer> set new HashSet<>();for (int x : nums)set.add(x);Object[] a…

时间序列与 statsmodels:预测所需的基本概念(2)

时间序列与 statsmodels&#xff1a;预测所需的基本概念&#xff08;2&#xff09; 维托米尔约万诺维奇 跟随 出版于 走向发展 4 分钟阅读 2022 年 1 月 31 日 8 一、说明 在使时间序列平稳后&#xff0c;在本博客中我们应用 SARIMAX 预测并进行深入解释。 二、关于平稳性 …

算法设计与分析复习--求解最大子段和问题(分支法、动态规划)

文章目录 问题描述分治法动态规划法 问题描述 最大子段和问题&#xff1b; 洛谷P1115.最大子段和 分治法 利用归并排序的方法&#xff0c;但是由于是算最大子段和所以&#xff0c;并不能将它变成有序的&#xff0c;左边和右边的最大子段和通过调用函数&#xff0c;而中间的要…

SpringCloudAlibaba系列之Nacos服务注册与发现

目录 说明 认识注册中心 Nacos架构图 Nacos服务注册与发现实现原理总览 SpringCloud服务注册规范 服务注册 心跳机制与健康检查 服务发现 主流服务注册中心对比 小小收获 说明 本篇文章主要目的是从头到尾比较粗粒度的分析Nacos作为注册中心的一些实现&#xff0c;很…

「Tech初见」对epoll的理解

一、Motivation 通常&#xff0c;操作系统会为每个进程划分一个时间片的&#xff0c;在这个时间片内进程可以合法占有 cpu 进行一些计算任务。并当时间片结束后自动退回至就绪状态待命&#xff0c;等待下一次的调度 但是&#xff0c;有一种情况会使进程提前&#xff08;时间片…

Web实战:基于Django与Bootstrap的在线计算器

文章目录 写在前面实验目标实验内容1. 创建项目2. 导入框架3. 配置项目前端代码后端代码 4. 运行项目 注意事项写在后面 写在前面 本期内容&#xff1a;基于Django与Bootstrap的在线计算器 实验环境&#xff1a; vscodepython(3.11.4)django(4.2.7)bootstrap(3.4.1)jquery(3…

1、cvpr2024

CVPR2024官网&#xff1a; Overleaf模板&#xff1a; 更改作者&#xff08;去掉CVPR标识&#xff09; % \usepackage{cvpr} % To produce the CAMERA-READY version \usepackage[review]{cvpr} % To produce the REVIEW version改成 \usepackage{cvpr} …

性格懦弱怎么办?如何改变懦弱的性格?

性格懦弱是一个比较常见的话题了&#xff0c;懦弱带来的苦恼和困扰&#xff0c;深深影响着我们的生活&#xff0c;人际关系&#xff0c;以及事业的发展。然后如何摆脱懦弱&#xff0c;却并非易事&#xff0c;尤其是对于成年人来说&#xff0c;这种懦弱的性格特征&#xff0c;已…

Prometheus+Grafana监控

Prometheus是一种开源监控系统&#xff0c;可用于收集指标和统计数据&#xff0c;并提供强大的查询语言&#xff0c;以便分析和可视化这些数据。它被广泛用于云原生和容器化环境中&#xff0c;可以嵌入到Kubernetes集群中&#xff0c;并与其他Kubernetes工具进行集成。 Grafan…

大模型的交互能力

摘要&#xff1a; 基础大模型显示出明显的潜力&#xff0c;可以改变AI系统的开发人员和用户体验&#xff1a;基础模型降低了原型设计和构建AI应用程序的难度阈值&#xff0c;因为它们在适应方面的样本效率&#xff0c;并提高了新用户交互的上限&#xff0c;因为它们的多模式和生…

代码随想录算法训练营|五十六天

回文子串 647. 回文子串 - 力扣&#xff08;LeetCode&#xff09; dp含义&#xff1a;表示区间内[i,j]是否有回文子串&#xff0c;有true&#xff0c;没有false。 递推公式&#xff1a;当s[i]和s[j]不相等&#xff0c;false&#xff1b;相等时&#xff0c;情况一&#xff0c;…

图书管理系统 保姆级教学 手把手教你图书管理系统设计!

天梯无捷径&#xff0c;唯有苦攀登。 一起加油&#xff0c;小伙伴们&#xff01;&#xff01; 目录 1. 实现思路: 2. 那么如何找对象呢? 3. Book类的实现 Book类总代码&#xff1a; 4. BookList类的实现 BookList类总代码&#xff1a; 5. 用户的操作 5.1 AddOperation类…

在线识别二维码工具

具体请前往&#xff1a;在线二维码识别解码工具--在线识别并解码二维码网址等内容

10、背景分离 —— 大津算法

上一节学习了通过一些传统计算机视觉算法,比如Canny算法来完成一个图片的边缘检测,从而可以区分出图像的边缘。 今天再看一个视觉中更常见的应用,那就是把图片的前景和背景的分离。 前景和背景 先看看什么是前景什么是背景。 在图像处理和计算机视觉中,"前景"…

Go——一、Go语言安装及介绍

Go 一、Windows下安装Go1、下载Go2、配置环境变量3、下载Jetbrain下的GoLang4、编写hello world5、编译和执行 二、Go语言介绍1、开发文档2、Go语言核心开发团队3、为什么要创建Go4、Go语言发展史5、Go语言特点6、Golang执行过程6.1 执行过程分析6.2 编译是什么 7、开发注意事项…

线性变换概论

线性变换 定义 设 V V V 和 W W W 都是在域 K K K上定义的向量空间&#xff0c; T : V → W T :V \rightarrow W T:V→W 对任二向量 x , y ∈ V x,y \in V x,y∈V,与任何标量 a ∈ K a \in K a∈K&#xff0c;满足&#xff1a; T ( x y ) T ( x ) T ( y ) T(xy)T(x)T(…

c语言:解决数组有关的删除,排序,合并等问题。

题目1&#xff1a;判断数组是否有序&#xff08;升序或者降序&#xff09; 思路和代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() {int a 0;scanf("%d", &a);int arr[50];int flag1 0;//是降序int flag2 0;//是升序…

系列十一、你平时工作用过的JVM常用基本配置参数有哪些?

一、常用参数 1.1、-Xms 功能&#xff1a;初始内存大小&#xff0c;默认为物理内存的1/64&#xff0c;等价于 -XX:InitialHeapSize 1.2、-Xmx 功能&#xff1a;最大分配内存&#xff0c;默认为物理内存的1/4&#xff0c;等价于 -XX:MaxHeapSize 1.3、-Xss 功能&#xff1a;设置…