C++ 异常

文章目录

  • [1. 异常概念](#1. 异常概念)
  • [2. 异常的抛出与捕获](#2. 异常的抛出与捕获)
    • [2.1 异常抛出](#2.1 异常抛出)
    • [2.2 异常捕获](#2.2 异常捕获)
    • [2.3 异常抛出与捕获逻辑:栈展开](#2.3 异常抛出与捕获逻辑:栈展开)
    • [2.4 异常抛出和捕获:特殊情况](#2.4 异常抛出和捕获:特殊情况)
      • [2.4.1 throw](#2.4.1 throw)
      • [2.4.2 catch](#2.4.2 catch)
  • [3. 自定义异常类型](#3. 自定义异常类型)
    • [3.1 自定义异常类型示例](#3.1 自定义异常类型示例)
  • [4. 异常安全问题](#4. 异常安全问题)
    • [4.1 noexcept关键字和操作符](#4.1 noexcept关键字和操作符)

1. 异常概念

C中处理错误的方式一般通过函数的异常返回值,设置错误码errno的形式实现。这种方式错误码提供的信息有限,并不能很好显示异常问题。

因此,C++中引入全新的一套错误处理机制:异常。

异常的处理中,涉及异常的抛出与捕获,实际抛出的异常本质是一个具体对象,类型是任意的,捕获时需要注意类型匹配,才可正确捕获。

2. 异常的抛出与捕获

2.1 异常抛出

异常抛出使用trythrow的结合。

cpp 复制代码
try
{
	throw exception;
}

try后必须接{}所含的语句块,throw异常的行为,必须在try中进行,只有在try中抛出的异常,才会被捕获。

2.2 异常捕获

异常捕获使用catch

cpp 复制代码
catch(type exception)
{

}

catch后,()内是用于接收所抛出异常的对象,{}catch处理异常的语句块,与try相同,必须采用语句块形式。

2.3 异常抛出与捕获逻辑:栈展开

实际程序中,会涉及到函数的多层调用,即函数中调用函数

如果在一个函数栈帧较深的函数中,抛出一个异常,且每层函数调用中,均有相应的捕获逻辑,会有什么行为呢?

异常的捕获:优先匹配最近的且类型能够匹配的catch。

在整个catch的匹配过程中,对于调用链上不匹配的catch所对应的函数栈帧会直接退出,剩余的代码将不会再被执行。 直至跳转到匹配的catch,就继续维护相应的函数栈帧,从其catch开始,继续执行之后的代码,直至函数返回。

明白整个异常抛出与捕获过程,自然会产生一个问题?抛出异常,往往是抛出一个函数栈内的对象,如果在当前函数栈内不匹配,回退到别的函数栈中匹配,那么此时这个对象不就被销毁了吗?

实际上,真正捕获的异常对象,并不是最初抛出的某个函数栈内的对象,编译器处理异常时,实际会构造一个新的异常对象(自定义类型,通常都是移动构造,转移资源),这个异常对象的生命周期持续整个catch匹配的过程,直至执行完所匹配catch的相关语句后,才被销毁。

这整个异常抛出和捕获的逻辑,其中函数调用链逐渐回退,直至匹配到相应catch,就被称为栈展开。

2.4 异常抛出和捕获:特殊情况

2.4.1 throw

如果一个异常抛出,直到回到main函数中,都没有匹配的catch,会怎样?

这种情况是非法的,一个异常如果最终未被捕获,那么当前进程就会被异常终止。
同时,这也就说明一点,抛出异常的行为必须在try中进行,只有在try中抛异常方具备被捕获的必要条件。

异常捕获,除了指明类型外,还可以进行任意类型捕获,即任意类型的异常到来,都可匹配。

2.4.2 catch

cpp 复制代码
catch(...)
{
}

上述写法便是任意类型捕获。

此外,已经被catch捕获的异常,可被再次抛出吗?可以在catch中抛出捕获的异常,且在catch中抛出的异常,与正常抛出异常行为无差别。

cpp 复制代码
try
{
	throw exception;
}
catch(const type& exception)
{
	throw;//可以直接写throw,默认将捕获到的异常抛出
}

3. 自定义异常类型

实际工程中,一般都是专门的异常类型对象,这个异常类型可以是自定义的,也可是库中提供的。

比起抛出其它类型,专门的异常类型中包含更多的相关错误信息,能帮助程序员更好地进行错误的了解和处理。

上图中的exception表示C++库中专门实现的异常类所在的头文件,包含了相关异常类型的声明和实现,本质是一个.hpp文件。

异常类型一般也会设计父类与子类的继承体系 ,实际抛出异常时,可能抛出一个父类异常,也可能抛出子类异常,此时有两种捕获方案:写多个catch,对父类和子类异常分别进行捕获;只对父类异常进行捕获,本质是利用多态机制 ,实际的子类对象可以给父类类型的引用,实际调用成员函数时,通过运行时多态,执行相应的函数。
实时使用时,一般不写多个catch捕获,而是统一使用父类类型的引用去统一捕获父类或子类的异常对象。

3.1 自定义异常类型示例

下面展示一个自行设计的异常类型,并完成抛出与捕获:

cpp 复制代码
class Exception
{
public:
	Exception(int id,const std::string& str):_id(id),_str(str)
	{ }

	virtual std::string what()
	{
		std::string str = _str + ' ' + std::to_string(_id);
		return std::move(str);
	}
	virtual ~Exception()
	{ }
	
protected:
	int _id;//出错编号
	std::string _str;//出错信息
};

class DevException :public Exception
{
public:
	DevException(int id, const std::string& str, int client_num):Exception(id,str),_client_num(client_num)
	{}

	std::string what() override 
	{
		std::string str = _str + ' ' + std::to_string(_id) + ' ' + std::to_string(_client_num);
		return std::move(str);
	}

	~DevException() override
	{ }

private:
	int _client_num;//出错员工号
};


int main()
{
	try
	{
		throw DevException(1, "测试继承异常中的抛出与捕获",100);
	}
	catch (Exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	return 0;
}

异常自定义类型中,通常都会提供一个what接口,返回一个string对象,用于将相关异常错误信息打印输出。

4. 异常安全问题

C++11中,异常引入为错误处理带来极大遍历,但同时也引入异常安全问题。

最常见的异常安全问题就是内存泄漏。

例如,在存在多条new语句的时候,由于每条new语句都存在抛出异常的可能,而由于异常抛出与捕获的栈展开逻辑,如果不再每条new语句后都添加catch逻辑,最终很可能因为栈展开的跳转逻辑,引发内存泄漏(通常对于第一个new之后的new)。而如果每条new语句后,都添加catch,代码又会显得非常冗余。

为了解决异常安全问题,尤其是异常安全中的内存泄漏,C++中又引入智能指针,以RAII风格进行资源申请与释放。

4.1 noexcept关键字和操作符

noexcept是一个关键字和编译期操作符。

关键字时,用于声明一个函数不能抛出异常。如果一个函数可能抛出异常,声明为noexcept,通常编译不会报错,但是如果实际运行时,抛出异常,当前进程会被异常终止。

操作符时,用于检测一个表达式是否可能抛出异常,可能抛出异常,返回flase;否则,返回true。
比如通过noexcept() 检测一个函数调用表达式是否会抛出异常,不过这个检测实际会受函数noexcept声明的影响------一个函数可能抛出异常,但声明为noexcept,进行判断,仍会返回true。

相关推荐
SamDeepThinking2 小时前
用设计模式重构核心业务代码的一次实战
java·后端·设计模式
endcy20162 小时前
mybatis-plus多租户兼容多字段租户标识
java·mybatis-plus·多租户
小伟童鞋2 小时前
c++中导出函数调用约定为__stdcall类型函数并指定导出函数名称
开发语言·c++
维C泡泡2 小时前
C++初认、命名规则、输入输出、函数重载、引用+coust引用
开发语言·c++
青草地溪水旁3 小时前
设计模式(C++)详解——建造者模式(2)
c++·设计模式·建造者模式
李游Leo3 小时前
Redis 持久化与高可用实践(RDB / AOF / Sentinel / Cluster 全解析)
java·spring·bootstrap
郝学胜-神的一滴3 小时前
深入探索 C++ 元组:从基础到高级应用
开发语言·c++·stl·软件工程
mask哥3 小时前
详解mcp以及agen架构设计与实现
java·微服务·flink·大模型·ai agent·springai·mcp
Propeller4 小时前
【Android】View 交互的事件处理机制
android·java