C++ 异常

异常处理作为C++中错误处理的核心机制,有效解决了传统错误码处理方式的诸多弊端,实现了异常检测与处理逻辑的分离。在实际编程场景中,程序运行时难免出现除零、内存分配失败、越界访问等异常情况,若未妥善处理极易导致程序崩溃。本文将结合一段典型的异常安全代码,从异常捕获、栈展开、资源释放、异常重新抛出等角度,深度剖析C++异常处理的底层逻辑与使用细节,帮助大家构建更健壮、安全的程序。


一、异常的概念及使用

1.1 异常的概念

核心思想:将问题检测与解决过程分离,程序的一部分负责检测问题并抛出异常对象,另一部分负责捕获并处理异常,检测环节无需知晓处理模块的全部细节。

对比C语言错误处理:C语言主要通过错误码(如-1、0)处理错误,需要手动查询错误信息,流程繁琐;C++异常通过抛出对象传递更全面的错误信息,便于定位和处理。

1.2 异常的抛出和捕获

抛出异常:程序出现问题时,通过throw关键字抛出一个对象,异常的类型和当前调用链决定了由哪个catch处理。

匹配规则:被选中的处理代码是调用链中与该对象类型匹配且离抛出位置最近的catch。

执行流程:

  1. throw执行后,其后的语句不再执行,程序直接跳转到匹配的catch模块。

  2. 控制权从throw位置转移到catch位置,意味着:

调用链上的函数可能提前退出。

异常处理程序执行时,调用链中创建的局部对象会被销毁(栈展开)。

异常对象拷贝:抛出异常对象时,会生成一个拷贝对象(类似函数传值返回),该拷贝对象在catch子句处理完毕后销毁。

1.3 栈展开

定义:抛出异常后,程序暂停当前函数执行,逐层向上查找匹配catch子句的过程称为栈展开。

查找流程:

  1. 检查throw是否在当前函数的try块内,若在则查找匹配的catch语句,匹配则跳转处理。

  2. 若当前函数无try/catch或类型不匹配,则退出当前函数,继续在上层调用函数中查找。

  3. 若回溯到main函数仍无匹配catch,程序会调用标准库terminate函数终止运行。

  4. 若找到匹配catch,处理完毕后catch后的代码会继续执行。

示例流程:func1() 抛出异常 → func2() 无匹配catch → func3() 无匹配catch → main() 查找catch

cpp 复制代码
#include <iostream>
#include <string> // 必须包含string头文件才能使用std::string
using namespace std;

// 除法函数:处理除零异常,内部包含try-catch
double Divide(int a, int b)
{
    try
    {
        // 异常检测:当除数b为0时,抛出string类型异常
        if (b == 0)
        {
            // 创建string对象存储错误信息
            string s("Divide by zero condition!");
            // 抛出异常对象,程序将跳转到最近的匹配catch块
            throw s;
        }
        else
        {
            // 正常情况:将int强转为double后计算除法并返回
            return ((double)a / (double)b);
        }
    }
    // 捕获int类型异常(注意:当前抛出的是string类型,此catch块无法匹配)
    // Divide 函数直接终止,不会执行 catch 之后的任何代码,包括那行 cout 和 return 0;。
    catch (int errid)
    {
        // 打印捕获的int类型错误码
        cout << errid << endl;
    }

    cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
    return 0;
}

// 业务函数:调用Divide并尝试捕获const char*类型异常
void Func()
{
    int len, time;
    cin >> len >> time;

    try
    {
        cout << Divide(len, time) << endl;
    }
    // 捕获const char*类型异常(当前抛出的是string类型,此catch块无法匹配)
    // Func 函数也因此直接终止,不会执行 catch 之后的 cout 行。
    catch (const char* errmsg)
    {
        // 打印捕获的const char*类型错误信息
        cout << errmsg << endl;
    }

    cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
}

int main()
{
    while (1)
    {
        try
        {
            Func();
        }
        // 捕获string类型异常(与Divide中抛出的string类型匹配)
        catch (const string& errmsg)
        {
            // 打印捕获的string类型错误信息
            cout << errmsg << endl;
        }
    }

    return 0;
}

1.4 查找匹配的处理代码

类型匹配规则:

  1. 一般要求抛出对象和catch类型完全匹配,多个匹配时选择离抛出位置最近的catch。

  2. 允许的类型转换(权限缩小):

非非常量 → 常量 数组 → 指向数组元素类型的指针

函数 → 指向函数的指针 派生类 → 基类(实际中继承体系异常设计的核心方式)

兜底处理:main函数最后通常使用catch(...)捕获所有类型异常,避免程序直接终止,但无法获取具体错误信息。

cpp 复制代码
// 包含所需头文件
#include <iostream>
#include <string>
#include <cstdlib>   // rand、srand
#include <ctime>     // time
#include <thread>    // this_thread::sleep_for
#include <chrono>    // seconds

using namespace std;

// 项目通用异常基类
// 大型项目中,各模块自定义异常都继承该基类
class Exception
{
public:
	// 构造函数:错误信息 + 错误编号
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}

	// 虚函数:提供错误信息,支持多态重写
	virtual string what() const
	{
		return _errmsg;
	}

	// 获取错误ID
	int getid() const
	{
		return _id;
	}

protected:
	string _errmsg;  // 错误描述信息
	int _id;         // 错误编码
};

// SQL模块异常:继承Exception基类
class SqlException : public Exception
{
public:
	// 新增sql语句字段,记录出错SQL
	SqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id)
		, _sql(sql)
	{}

	// 重写what,拼接模块名+错误信息+出错SQL
	virtual string what() const
	{
		string str = "SqlException:";
		str += _errmsg;
		str += "->";
		str += _sql;
		return str;
	}

private:
	const string _sql;  // 出错的SQL语句
};

// 缓存模块异常
class CacheException : public Exception
{
public:
	CacheException(const string& errmsg, int id)
		:Exception(errmsg, id)
	{}

	// 重写what,标识Cache模块
	virtual string what() const
	{
		string str = "CacheException:";
		str += _errmsg;
		return str;
	}
};

// HTTP服务模块异常
class HttpException : public Exception
{
public:
	// 新增请求类型type:get/post等
	HttpException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{}

	// 重写what,拼接模块+请求类型+错误信息
	virtual string what() const
	{
		string str = "HttpException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}

private:
	const string _type;  // 请求类型 get/post
};

// SQL模块函数:随机抛出SqlException
void SQLMgr()
{
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	}
	else
	{
		cout << "SQLMgr 调用成功" << endl;
	}
}

// 缓存模块函数:随机抛出CacheException,内部调用SQLMgr
void CacheMgr()
{
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}
	else
	{
		cout << "CacheMgr 调用成功" << endl;
	}

	SQLMgr();  // 调用下层SQL模块
}

// HTTP服务入口:随机抛出HttpException,内部调用CacheMgr
void HttpServer()
{
	if (rand() % 3 == 0)
	{
		throw HttpException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpException("权限不足", 101, "post");
	}
	else
	{
		cout << "HttpServer调用成功" << endl;
	}

	CacheMgr();  // 调用下层缓存模块
}

// 主函数:循环启动服务,统一捕获基类Exception
int main()
{
	srand(time(0));  // 设置随机数种子

	while (1)
	{
		// 每隔1秒执行一次,方便观察
		this_thread::sleep_for(chrono::seconds(1));

		try
		{
			HttpServer();
		}
		// 捕获基类引用,可捕获所有派生类异常(多态)
		catch (const Exception& e)
		{
			// 多态调用对应子类的what()
			cout << e.what() << endl;
		}
		// 兜底捕获:防止未知类型异常导致程序崩溃
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}

核心知识点

  1. 自定义异常继承体系设计

基类 Exception:提供统一接口 what()、getid();包含公共字段:_errmsg、_id

派生类:SqlException、CacheException、HttpException;各自扩展私有成员(SQL语句、请求类型等);重写 what(),输出带模块标识的错误信息

  1. 多态在异常中的应用

抛出派生类对象,用基类引用 const Exception& 捕获;调用 e.what() 会多态匹配到子类重写版本

优点:上层只需捕获基类,无需关心具体异常类型

  1. 异常传播与栈展开

调用链:main → HttpServer → CacheMgr → SQLMgr

任意一层抛异常,会逐层退出函数; 直到找到匹配 catch,中间所有未执行代码都被跳过

  1. 工程规范优点

1) 统一异常入口,便于日志收集、监控告警

2) 各模块错误信息可携带上下文(SQL、请求类型)

3) 基类捕获,代码简洁、扩展性强(新增模块只需新增派生类)

4) catch(...) 兜底,避免程序直接崩溃

  1. 运行效果说明

大概率打印:调用成功链路

小概率随机抛出:HttpException:get:请求资源不存在;CacheException:数据不存;SqlException:权限不足->select * from name = '张三'

1.5 异常重新抛出

场景:捕获异常后需要先处理局部资源(如释放内存、锁),再将异常传递给外层调用链处理。

语法:throw;(无参数),直接将当前捕获的异常对象原封不动重新抛出。

cpp 复制代码
// 包含头文件
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <thread>
#include <chrono>

using namespace std;

// 异常基类
class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

	int getid() const
	{
		return _id;
	}
protected:
	string _errmsg;
	int _id;
};

// HTTP异常类
class HttpException : public Exception
{
public:
	HttpException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{}

	virtual string what() const
	{
		string str = "HttpException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}

private:
	const string _type;
};

// 底层发送消息函数:模拟网络/业务异常
void _SendMsg(const string& s)
{
	// 50%概率抛网络异常(102)
	if (rand() % 2 == 0)
	{
		throw HttpException("网络不稳定,发送失败", 102, "put");
	}
	// 约1/7概率抛非网络异常(103)
	else if (rand() % 7 == 0)
	{
		throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
	}
	// 正常发送成功
	else
	{
		cout << "发送成功" << endl;
	}
}

// 上层发送接口:带重试机制
void SendMsg(const string& s)
{
	// 总共尝试 4 次(首次+3次重试)
	for (size_t i = 0; i < 4; i++)
	{
		try
		{
			// 调用底层发送接口
			_SendMsg(s);
			// 发送成功,跳出循环
			break;
		}
		catch (const Exception& e)
		{
			// 异常码102:网络不稳定,允许重试
			if (e.getid() == 102)
			{
				// 第4次(i=3)仍然失败,不再重试,重新抛出异常
				if (i == 3)
					throw;

				// 前三次失败,打印重试日志
				cout << "开始第" << i + 1 << "次重试" << endl;
			}
			else
			{
				// 非网络异常(如103非好友),直接重新抛出,不重试
				throw;
			}
		}
	}
}

// 主函数测试
int main()
{
	srand(time(0));	// 设置随机数种子
	string str;
	// 循环输入消息发送
	while (cin >> str)
	{
		try
		{
			SendMsg(str);
		}
		catch (const Exception& e)
		{
			// 捕获最终异常并打印
			cout << e.what() << endl << endl;
		}
		catch (...)
		{
			// 兜底捕获未知异常
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}

核心逻辑与知识点

  1. 业务场景:模拟聊天发消息

网络差(异常码 102):允许重试 3 次

非网络错误(103 非好友):不重试,直接抛给上层

重试 3 次仍失败:不再尝试,抛出异常

  1. 关键语法:throw; 重新抛出

只能在 catch 块内使用;抛出原异常对象,而非拷贝;异常继续向上层调用链传递

  1. 执行流程

场景①:发送成功:_SendMsg 无异常 → 执行 break → 退出循环 → 发送完成

场景②:102 网络异常(前3次失败):捕获 → 打印重试 → 下一轮循环继续发送

场景③:102 第4次失败:i == 3 → throw; → 异常抛到 main 函数处理

场景④:103 非网络异常:直接 throw; → 不重试 → 抛到 main 处理

  1. 工程设计亮点

1) 分层处理:底层抛异常,上层做重试策略

2) 精细化异常处理:按错误码区分是否重试

3) 异常不丢失:可重试的用完次数再抛出,不可重试直接抛出

4) 上层无感知:main 只需要统一捕获基类异常即可

运行结果示例

cpp 复制代码
hello
开始第1次重试
开始第2次重试
发送成功

test
HttpException:put:你已经不是对象的好友,发送失败

123
开始第1次重试
开始第2次重试
开始第3次重试
HttpException:put:网络不稳定,发送失败

1.6 异常安全问题

资源泄漏风险:异常抛出后,try块后代码不再执行,若之前申请了资源(内存、锁等)且未释放,会导致资源泄漏。

解决方案:

  1. 手动捕获异常,释放资源后重新抛出(如示例中Func()函数释放array)。

  2. 推荐使用RAII(资源获取即初始化):通过智能指针(unique_ptr、shared_ptr)或标准容器(vector)管理资源,利用栈对象析构函数自动释放资源,天生保证异常安全。

析构函数异常:析构函数中抛出异常需谨慎处理,否则会导致部分资源未释放(《Effective C++》条款8:别让异常逃离析构函数)。

代码示例

cpp 复制代码
#include <iostream>
using namespace std; 

// 除法函数:当除数b为0时抛出异常,否则返回计算结果
double Divide(int a, int b)
{
    // 异常检测:除数为0是非法运算,主动抛出异常
    if (b == 0)
    {
        // 抛出const char*类型的异常对象,携带错误信息
        throw "Division by zero condition!";
    }
    // 正常情况:将int强转为double后计算除法并返回
    return (double)a / (double)b;
}

// 业务函数:包含动态内存申请与异常安全处理
void Func()
{
    // 动态申请10个int大小的内存,array指向这块堆内存
    int *array = new int[10];
    try
    {
        int len, time;
        // 从标准输入读取两个整数,作为除法的被除数和除数
        cin >> len >> time;
        // 调用Divide函数,若触发除0异常则进入catch块
        cout << Divide(len, time) << endl;
    }
    // 捕获所有类型的异常(兜底捕获),用于处理资源释放
    catch (...)
    {
        // 打印释放内存的日志,便于调试观察
        cout << "delete []" << array << endl;
        // 释放动态申请的堆内存,避免内存泄漏
        delete[] array;
        // 重新抛出当前捕获的异常,将错误传递给上层调用者处理
        throw; 
    }
    // 无异常时执行:正常释放堆内存
    cout << "delete []" << array << endl;
    delete[] array;
}

// 主函数:程序入口,负责最终的异常捕获与处理
int main()
{
    try
    {
        Func();
    }
    // 捕获const char*类型的异常(对应Divide中抛出的字符串异常)
    catch (const char *errmsg)
    {
        // 打印异常信息
        cout << errmsg << endl;
    }
    // 捕获标准库异常类(继承自std::exception的异常)
    catch (const exception &e)
    {
        // 调用what()方法获取标准异常的描述信息并打印
        cout << e.what() << endl;
    }
    // 兜底捕获:捕获所有未被前面catch处理的异常类型
    catch (...)
    {
        cout << "Unkown Exception" << endl;
    }
    return 0;
}
  1. 正常情况(不抛异常)

流程:执行 try 里的代码;没有异常,不进 catch;执行完 try 块,继续往下走;走到 try 后面的 delete[] array。

  1. 异常情况(Divide 抛异常)

流程:try 里代码抛出异常;立刻跳转到 catch(...);在 catch 里执行 delete[] array(释放内存);然后 throw; 把异常往外抛;直接离开 Func 函数,try 后面的 delete 根本走不到。

核心逻辑梳理

  1. 异常传递流程:

Divide 检测到除0错误 → 抛出 const char* 异常

Func 捕获所有异常 → 释放堆内存 → 重新抛出原异常

main 捕获最终异常 → 打印错误信息

  1. 异常安全设计:

无论是否发生异常,array 指向的堆内存都会被释放(异常时在 catch 中释放,正常时在 try 后释放)

异常不会被"吞掉",最终会传递到 main 函数处理,保证错误可感知

  1. 注意点:

catch (...) 是兜底捕获,只能处理资源清理,无法获取异常具体信息

重新抛出 throw; 必须在 catch 块内使用,会传递原异常对象(而非拷贝)

典型应用:发送消息失败重试时,捕获网络异常(ID=102)重试3次,其他异常直接重新抛出;若重试3次仍失败,也重新抛出异常。

1.7 异常规范

C++98 异常规范:

函数参数列表后接throw(类型1, 类型2...):表示函数可能抛出的异常类型。

throw():表示函数不抛出任何异常。

缺点:语法复杂,实践中较少使用。

C++11 简化规范:

noexcept:表示函数不会抛出异常。

编译器仅做编译期检查,若noexcept函数内包含throw或调用可能抛异常的函数,仍可编译通过(部分编译器报警告),但运行时若抛出异常,程序会调用terminate终止。

noexcept运算符:noexcept(表达式)可检测表达式是否会抛出异常,会抛返回false,不会抛返回true。

cpp 复制代码
#include <iostream>
#include <list>  // 用于 list 容器
using namespace std;

// 除法函数:除数为0时抛出 const char* 异常
double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	return (double)a / (double)b;
}

int main()
{
	int i = 0;

	// noexcept(表达式):判断表达式是否**不会抛出异常**
	// 不会抛 → 返回 1(true)
	// 可能抛 → 返回 0(false)

	// 1. Divide(1,2):逻辑上不会抛异常,但函数**本身没声明noexcept**
	cout << noexcept(Divide(1, 2)) << endl;

	// 2. Divide(1,0):会执行 throw,但 noexcept 只看**函数声明**,不看运行逻辑
	cout << noexcept(Divide(1, 0)) << endl;

	// 3. ++i:内置int自增,绝不会抛异常
	cout << noexcept(++i) << endl;

	list<int> lt;

	// 4. lt.begin():标准库迭代器接口,声明为noexcept
	cout << noexcept(lt.begin()) << endl;

	return 0;
}

运行结果

cpp 复制代码
0
0
1
1

逐行解释:

  1. noexcept(Divide(1,2)) → 0

Divide 函数没有加 noexcept 修饰,编译器就认为它可能抛异常,所以返回 0。

  1. noexcept(Divide(1,0)) → 0

noexcept只看函数声明,不看运行逻辑。哪怕你传0一定会抛,只要函数没写noexcept,结果就是0。

  1. noexcept(++i) → 1

内置类型算术操作,C++ 规定绝不会抛异常。

  1. noexcept(lt.begin()) → 1

标准库 list::begin() 被声明为 noexcept,所以返回 1。

noexcept 核心知识点

  1. noexcept(表达式) 是运算符:编译期求值,返回 true/false,用来判断表达式是否保证不抛异常。

  2. 判断依据只有一个:函数/操作 有没有被声明为 noexcept,和运行时会不会真抛异常无关。

  3. 什么时候返回 1:内置运算(+ - * / ++ -- 等);标库容器大部分查询、迭代器接口;自己写函数时后面加了 noexcept

  4. 什么时候返回 0:没加 noexcept 的普通函数;可能抛异常的标准库函数(如 new、vector::push_back 等)

如果给 Divide 加上 noexcept

cpp 复制代码
double Divide(int a, int b) noexcept
{
	if (b == 0) throw "..."; // 语法合法,但运行时抛会直接 terminate
	return (double)a / b;
}
cpp 复制代码
noexcept(Divide(1,2)) → 1
noexcept(Divide(1,0)) → 1

哪怕运行时会崩,编译期 noexcept 结果依然是 1。

  1. 语法层面:合法

noexcept 是声明性关键字,不是运行时检查。函数内部写 throw 语法上不报错,编译器不会阻止你。

  1. 运行时行为:核心规则

一旦被 noexcept 修饰的函数抛出异常,程序会直接调用 std::terminate() 终止运行。

不会发生:不会栈展开;不会去找外层 catch;不会执行析构、释放资源;直接:程序崩溃退出。

  1. 为什么会这样?noexcept 的含义是:我向编译器保证:这个函数绝对不会抛任何异常。

编译器基于这个承诺做优化:不生成异常展开相关代码;不保留栈回溯信息。如果你违背承诺 throw,编译器没有兜底机制,只能直接 terminate。

  1. 和普通函数对比

1) 不加 noexcept(正常情况):抛出异常 → 栈展开 → 被 catch 捕获 → 程序继续运行

cpp 复制代码
double Divide(int a, int b) {
    if (b == 0) throw "...";
    return (double)a / b;
}

2) 加 noexcept:抛出异常 → 直接 terminate → 程序退出,没有捕获机会

cpp 复制代码
double Divide(int a, int b) noexcept {
    if (b == 0) throw "...";
    return (double)a / b;
}
  1. noexcept 正确用法:只用于确实不会抛异常的函数
cpp 复制代码
double Divide(int a, int b) noexcept {
    return (double)a / b;  // 不抛异常
}

// 或者:内部处理错误,绝不抛出去
double Divide(int a, int b) noexcept {
    if (b == 0) {
        cerr << "除零错误" << endl;
        return 0;  // 不 throw
    }
    return (double)a / b;
}
  1. 结合 noexcept(表达式) 看
cpp 复制代码
cout << noexcept(Divide(1, 0)) << endl;  // 输出:1

编译期只看函数声明有 noexcept;不管运行时会不会崩,结果都是 1(承诺不抛)

  1. 总结

1) noexcept 是承诺,不是检查。

2) 承诺不抛,却抛了 → 直接 terminate,程序崩。

3) noexcept(expr) 只看声明,不看运行逻辑。

二、标准库的异常

2.1 标准异常继承体系

基类:std::exception,所有标准库异常均继承自该类。

核心接口:virtual const char* what() const;,用于获取异常描述信息,派生类可重写。

主要派生类:

|-----------------------|------------------------|
| 异常类 | 含义 |
| std::bad_alloc new | 分配内存失败 |
| std::bad_cast | 动态类型转换失败(dynamic_cast) |
| std::bad_typeid | typeid 操作空指针 |
| std::bad_exception | 异常处理中出现意外 |
| std::logic_error | 程序逻辑错误(可在运行前避免) |
| std::runtime_error | 运行时错误(仅运行时可检测) |
| std::domain_error | 数学域错误(如对数负数) |
| std::invalid_argument | 无效参数 |
| std::length_error | 超过容器最大长度 |
| std::out_of_range | 越界访问(如vector下标越界) |
| std::overflow_error | 算术溢出 |
| std::range_error | 范围错误 |
| std::underflow_error | 算术下溢 |

三、核心代码示例解析

示例1:基础异常抛出与捕获

cpp 复制代码
double Divide(int a, int b)
{
    if (b == 0)
    {
        throw "Division by zero condition!"; // 抛出const char*异常
    }
    return (double)a / (double)b;
}

void Func()
{
    int* array = new int[10];
    try
    {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    }
    catch (...) // 捕获所有异常
    {
        cout << "delete []" << array << endl;
        delete[] array; // 释放内存
        throw; // 重新抛出异常
    }
    cout << "delete []" << array << endl;
    delete[] array;
}

int main()
{
    try
    {
        Func();
    }
    catch (const char* errmsg) // 捕获const char*异常
    {
        cout << errmsg << endl;
    }
    catch (const exception& e) // 捕获标准库异常
    {
        cout << e.what() << endl;
    }
    catch (...) // 兜底捕获
    {
        cout << "Unkown Exception" << endl;
    }
    return 0;
}

执行流程(除0时):

  1. Divide 抛出const char*异常。

  2. 栈展开进入Func的catch(...),释放array内存。

  3. throw; 重新抛出异常,栈展开进入main的catch(const char*),打印错误信息。

示例2:自定义异常继承体系

cpp 复制代码
class Exception // 自定义异常基类
{
public:
    Exception(const string& errmsg, int id)
        :_errmsg(errmsg), _id(id)
    {}
    virtual string what() const
    {
        return _errmsg;
    }
    int getid() const
    {
        return _id;
    }
protected:
    string _errmsg;
    int _id;
};

class SqlException : public Exception // SQL异常
{
public:
    SqlException(const string& errmsg, int id, const string& sql)
        :Exception(errmsg, id), _sql(sql)
    {}
    virtual string what() const override
    {
        string str = "SqlException:";
        str += _errmsg + "->" + _sql;
        return str;
    }
private:
    const string _sql;
};

// 类似定义CacheException、HttpException...

int main()
{
    try
    {
        HttpServer(); // 可能抛出HttpException/CacheException/SqlException
    }
    catch (const Exception& e) // 捕获基类,处理所有派生类异常
    {
        cout << e.what() << endl; // 多态调用派生类what()
    }
    catch (...)
    {
        cout << "Unkown Exception" << endl;
    }
    return 0;
}

优势:通过基类Exception捕获所有派生类异常,简化代码且便于扩展。

示例3:异常重新抛出与重试

cpp 复制代码
void SendMsg(const string& s)
{
    for (size_t i = 0; i < 4; i++) // 重试3次
    {
        try
        {
            _SeedMsg(s); // 可能抛HttpException
            break;
        }
        catch (const Exception& e)
        {
            if (e.getid() == 102) // 网络不稳定,重试
            {
                if (i == 3) throw; // 重试3次失败,重新抛出
                cout << "开始第" << i+1 << "重试" << endl;
            }
            else
            {
                throw; // 其他异常直接重新抛出
            }
        }
    }
}

逻辑:仅对网络异常(ID=102)重试,其他异常直接传递给上层处理。

四、关键总结

  1. 异常核心机制:throw抛出异常对象 → 栈展开查找匹配catch → catch处理异常(可重新抛出)。

  2. 异常安全:优先使用RAII(智能指针/容器)管理资源,避免手动new/delete导致泄漏。

  3. 异常规范:C++11推荐noexcept标记不抛异常的函数,简化代码。

  4. 继承体系设计:自定义异常继承自基类Exception,通过基类捕获所有派生类异常,提高代码可扩展性。

  5. 兜底处理:main函数末尾必须加catch(...),防止程序意外终止。


综上,C++异常处理并非简单的try-catch-throw语法堆砌,而是一套完整的错误处理体系。从异常抛出与捕获匹配、栈展开机制,到异常重新抛出与资源安全释放,每一个细节都直接影响程序的稳定性与健壮性。只有深刻理解异常执行流程,遵循异常安全设计原则,合理运用RAII等思想规避资源风险,才能在实际开发中真正发挥异常机制的价值,写出更可靠、更易维护的C++代码。

相关推荐
lxh01131 小时前
嵌套数组生成器题解
开发语言·javascript·ecmascript
知识分享小能手1 小时前
Redis入门学习教程,从入门到精通,Redis服务配置知识点详解(3)
数据库·redis·学习
2401_884563241 小时前
高性能日志库C++实现
开发语言·c++·算法
q5431470871 小时前
mybatis plus打印sql日志
数据库·sql·mybatis
Dxy12393102161 小时前
DrissionPage使用js点击:突破常规交互限制的“隐形手”
开发语言·javascript·交互
czxyvX1 小时前
C++ - 基于多设计模式下的同步&异步日志系统
c++·设计模式
handler011 小时前
基础算法:BFS
开发语言·数据结构·c++·学习·算法·宽度优先
2401_879503411 小时前
C++中的状态模式实战
开发语言·c++·算法
@PHARAOH1 小时前
HOW - Go 开发入门(四)- ORM 对象关系映射
开发语言·后端·golang