【C++】异常处理 ⑥ ( 异常生命周期 | 抛出自定义类对象异常 | 自定义类对象异常的生命周期 | 抛出 自定义类引用类型 异常 | 抛出 自定义类指针类型 异常 )





一、C++ 异常处理 - 抛出自定义类对象异常




1、抛出 异常对象


如果 抛出的 指针类型 , 指向的是 实际的对象 , 那么就要涉及到 对象的 内存空间的 分配 与 释放 ;

涉及到 内存空间 的 申请 和 释放 , 就需要考 讨论 异常 的生命周期 , 什么时候申请内存 , 什么时候释放内存 ;


2、代码示例 - 抛出 异常对象


下面的代码中 , 声明了 3 个自定义类 Exception1 , Exception2 , Exception3 ;

在不同的时机 , 抛出不同的 自定义类 对象 ;

抛出异常 , 直接使用 throw 关键字抛出 , Exception1 对象在抛出时创建 ;

throw Exception1();

在 catch 分支中 , 拦截异常 , 此处拦截的是 异常对象 , 不是 指针 或 引用 ;

catch (Exception1 e)

代码示例 :

#include "iostream"
using namespace std;

// 异常类
class Exception1 {
    
    };
class Exception2 {
    
    };
class Exception3 {
    
    };

// 拷贝函数 
// 使用 throw 关键字抛出 对象异常
void my_strcpy(char* to, char* from) {
    
    
	if (from == NULL)
	{
    
    
		// 源字符串出错
		throw Exception1();
	}
	if (to == NULL)
	{
    
    
		// 目标字符串出错
		throw Exception2();
	}
	
	// 拷贝前检查条件
	if (*from == 'J') 
	{
    
    
		// 源字符串不能是 J 开头的
		throw Exception3();
	}

	// 逐个字节拷贝字符
	while (*from != '\0')
	{
    
    
		*to = *from;
		to++;
		from++;
	}
	*to = '\0';
}

int main() {
    
    

	// 源字符串
	char str1[] = "Jerry";
	// 目的字符串
	char str2[32] = {
    
    0};

	try
	{
    
    
		// 调用字符串拷贝函数
		my_strcpy(str2, str1);

		cout << "拷贝成功 : str2 : " << str2 << endl;
	}
	// catch 分支中可以写 异常变量 , 也可以不写
	// 如果不写 , 则不能访问抛出的 异常对象
	catch (Exception1 e)
	{
    
    
		cout << "出现 Exception1 异常 " << endl;
	}
	catch (Exception2 e)
	{
    
    
		cout << "出现 Exception2 异常 " << endl;
	}
	catch (Exception3 e)
	{
    
    
		cout << "出现 Exception3 异常 " << endl;
	}

	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
};

执行结果 :

在这里插入图片描述





二、讨论自定义类对象异常的生命周期




1、异常类设置 构造函数 / 析构函数 / 拷贝构造函数


为异常对象类设置 构造函数 , 析构函数 , 拷贝构造函数 ;

分析 异常对象 在不同的阶段 的 构造 和 析构 情况 ;

class Exception3 {
    
    
public:
	Exception3() {
    
    
		cout << "Exception3 构造函数" << endl;
	}
	Exception3(const Exception3& obj) {
    
    
		cout << "Exception3 拷贝构造函数" << endl;
	}
	~Exception3() {
    
    
		cout << "Exception3 析构函数" << endl;
	}
};

2、异常对象生命周期分析


异常对象生命周期分析 :

  • 调用构造函数 : 使用 throw 关键字 , 抛出异常 , 同时调用 Exception3 构造函数 ,
throw Exception3();
  • 调用拷贝构造函数 : 异常抛出后 , 在 try-catch 代码块中 , 需要将抛出的异常 传递到 拦截的异常变量处 , 此时调用 Exception3 的 拷贝构造函数 , 传递 异常变量 参数 ;
  • 调用析构函数 : catch 捕获异常分支的代码执行完毕后 , 在最后一个大括号 } 结尾 , 就会将 异常对象 析构掉 , 抛出的异常 和 传递的异常变量 都会同时被析构 ;
	// 抛出的异常 如果要在 catch 分支中访问
	// 需要调用 拷贝构造函数 将异常对象传递给 catch 分支中的异常变量
	catch (Exception3 e)
	{
    
    
		cout << "出现 Exception3 异常 " << endl;
	}

特别注意 : 此处有 2 个异常对象 , 一个 抛出的异常对象 , 在 异常处理 机制中 , 一个是捕获的异常对象 , 由 抛出异常对象 的 拷贝构造函数 拷贝构造而来 ;

异常处理完毕后 , 两个 异常对象 都要被析构掉 ;


代码示例 :

#include "iostream"
using namespace std;

// 异常类
class Exception1 {
    
    };
class Exception2 {
    
    };
class Exception3 {
    
    
public:
	Exception3() {
    
    
		cout << "Exception3 构造函数" << endl;
	}
	Exception3(const Exception3& obj) {
    
    
		cout << "Exception3 拷贝构造函数" << endl;
	}
	~Exception3() {
    
    
		cout << "Exception3 析构函数" << endl;
	}
};

// 拷贝函数 
// 使用 throw 关键字抛出 对象异常
void my_strcpy(char* to, char* from) {
    
    
	if (from == NULL)
	{
    
    
		// 源字符串出错
		throw Exception1();
	}
	if (to == NULL)
	{
    
    
		cout << "抛出 Exception3 对象 异常" << endl;
		// 目标字符串出错
		throw Exception2();
	}

	// 拷贝前检查条件
	if (*from == 'J')
	{
    
    
		// 源字符串不能是 J 开头的
		throw Exception3();
	}

	// 逐个字节拷贝字符
	while (*from != '\0')
	{
    
    
		*to = *from;
		to++;
		from++;
	}
	*to = '\0';
}

int main() {
    
    

	// 源字符串
	char str1[] = "Jerry";
	// 目的字符串
	char str2[32] = {
    
     0 };

	try
	{
    
    
		// 调用字符串拷贝函数
		my_strcpy(str2, str1);

		cout << "拷贝成功 : str2 : " << str2 << endl;
	}
	// catch 分支中可以写 异常变量 , 也可以不写
	// 如果不写 , 则不能访问抛出的 异常对象
	catch (Exception1 e)
	{
    
    
		cout << "出现 Exception1 异常 " << endl;
	}
	catch (Exception2 e)
	{
    
    
		cout << "出现 Exception2 异常 " << endl;
	}
	// 抛出的异常 如果要在 catch 分支中访问
	// 需要调用 拷贝构造函数 将异常对象传递给 catch 分支中的异常变量
	catch (Exception3 e)
	{
    
    
		cout << "出现 Exception3 异常 " << endl;
	}
	/*catch (Exception3& e)
	{
		cout << "出现 Exception3& 异常 " << endl;
	}*/

	cout << "try-catch 代码块执行完毕" << endl;

	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
};

执行结果 :

Exception3 构造函数
Exception3 拷贝构造函数
出现 Exception3 异常
Exception3 析构函数
Exception3 析构函数
try-catch 代码块执行完毕
请按任意键继续. . .

在这里插入图片描述


3、不拦截异常对象的生命周期分析


如果 try-catch 代码块中 , 只拦截 异常类型 , 没有声明拦截 异常变量 , 就不会调用 抛出异常的 拷贝构造函数 ;

	catch (Exception3)
	{
    
    
		cout << "出现 Exception3 异常 " << endl;
	}

从抛出异常到拦截异常打印的日志如下 :

Exception3 构造函数
出现 Exception3 异常
Exception3 析构函数
try-catch 代码块执行完毕
请按任意键继续. . .

代码示例 :

#include "iostream"
using namespace std;

// 异常类
class Exception1 {
    
    };
class Exception2 {
    
    };
class Exception3 {
    
    
public:
	Exception3() {
    
    
		cout << "Exception3 构造函数" << endl;
	}
	Exception3(const Exception3& obj) {
    
    
		cout << "Exception3 拷贝构造函数" << endl;
	}
	~Exception3() {
    
    
		cout << "Exception3 析构函数" << endl;
	}
};

// 拷贝函数 
// 使用 throw 关键字抛出 对象异常
void my_strcpy(char* to, char* from) {
    
    
	if (from == NULL)
	{
    
    
		// 源字符串出错
		throw Exception1();
	}
	if (to == NULL)
	{
    
    
		cout << "抛出 Exception3 对象 异常" << endl;
		// 目标字符串出错
		throw Exception2();
	}

	// 拷贝前检查条件
	if (*from == 'J')
	{
    
    
		// 源字符串不能是 J 开头的
		throw Exception3();
	}

	// 逐个字节拷贝字符
	while (*from != '\0')
	{
    
    
		*to = *from;
		to++;
		from++;
	}
	*to = '\0';
}

int main() {
    
    

	// 源字符串
	char str1[] = "Jerry";
	// 目的字符串
	char str2[32] = {
    
     0 };

	try
	{
    
    
		// 调用字符串拷贝函数
		my_strcpy(str2, str1);

		cout << "拷贝成功 : str2 : " << str2 << endl;
	}
	// catch 分支中可以写 异常变量 , 也可以不写
	// 如果不写 , 则不能访问抛出的 异常对象
	catch (Exception1 e)
	{
    
    
		cout << "出现 Exception1 异常 " << endl;
	}
	catch (Exception2 e)
	{
    
    
		cout << "出现 Exception2 异常 " << endl;
	}
	// 抛出的异常 如果要在 catch 分支中访问
	// 需要调用 拷贝构造函数 将异常对象传递给 catch 分支中的异常变量
	catch (Exception3)
	{
    
    
		cout << "出现 Exception3 异常 " << endl;
	}
	/*catch (Exception3& e)
	{
		cout << "出现 Exception3& 异常 " << endl;
	}*/

	cout << "try-catch 代码块执行完毕" << endl;

	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
};

执行结果 :

Exception3 构造函数
出现 Exception3 异常
Exception3 析构函数
try-catch 代码块执行完毕
请按任意键继续. . .

在这里插入图片描述





三、C++ 异常处理 - 抛出 自定义类引用类型 异常




1、不能同时拦截 对象类型 和 引用类型


在 try-catch 代码块中 , 不能同时拦截 对象类型 和 引用类型 ,

系统会将这两种类型 看做 同一种类型 ;

如下代码中 , 既拦截了 Exception3 e 对象类型 , 又拦截了 Exception3& e 引用类型 , 此时会报错 ;

	catch (Exception3 e)
	{
    
    
		cout << "出现 Exception3 异常 " << endl;
	}
	catch (Exception3& e)
	{
    
    
		cout << "出现 Exception3& 异常 " << endl;
	}

报错信息如下 :

1>------ 已启动生成: 项目: HelloWorld, 配置: Debug Win32 ------
1>Test.cpp
1>Y:\002_WorkSpace\002_VS\HelloWorld\HelloWorld\Test.cpp(82,2): error C2315: “Exception3 &: 引用由“Exception3”在行 78 上捕获
1>已完成生成项目“HelloWorld.vcxproj”的操作 - 失败。
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0==========

在这里插入图片描述


2、抛出 / 捕获 引用类型异常的声明周期分析


引用类型异常生命周期分析 :

  • 调用构造函数 : 使用 throw 关键字 , 抛出异常 , 同时调用 Exception3 构造函数 ,
throw Exception3();
  • 异常捕获 : 异常抛出后 , 在 try-catch 代码块中 , 可以直接通过引用类型的异常 Exception3& e , 访问到上述抛出的异常对象 , 不会再调用拷贝构造函数 ;
  • 调用析构函数 : catch 捕获异常分支的代码执行完毕后 , 在最后一个大括号 } 结尾 , 就会将 异常对象 析构掉 , 抛出的异常 会被析构 ;
	// 抛出的异常 如果要在 catch 分支中访问
	// 需要调用 拷贝构造函数 将异常对象传递给 catch 分支中的异常变量
	catch (Exception3 e)
	{
    
    
		cout << "出现 Exception3 异常 " << endl;
	}

特别注意 : 此处只有 1 个异常对象 , 就是 抛出的异常对象 ;

与 拦截 异常对象 相比 , 减少了一个异常对象 , 因此这里推荐 拦截 引用类型异常 ;

异常处理完毕后 , 这个 异常对象 要被析构掉 ;


代码示例 :

#include "iostream"
using namespace std;

// 异常类
class Exception1 {
    
    };
class Exception2 {
    
    };
class Exception3 {
    
    
public:
	Exception3() {
    
    
		cout << "Exception3 构造函数" << endl;
	}
	Exception3(const Exception3& obj) {
    
    
		cout << "Exception3 拷贝构造函数" << endl;
	}
	~Exception3() {
    
    
		cout << "Exception3 析构函数" << endl;
	}
};

// 拷贝函数 
// 使用 throw 关键字抛出 对象异常
void my_strcpy(char* to, char* from) {
    
    
	if (from == NULL)
	{
    
    
		// 源字符串出错
		throw Exception1();
	}
	if (to == NULL)
	{
    
    
		cout << "抛出 Exception3 对象 异常" << endl;
		// 目标字符串出错
		throw Exception2();
	}

	// 拷贝前检查条件
	if (*from == 'J')
	{
    
    
		// 源字符串不能是 J 开头的
		throw Exception3();
	}

	// 逐个字节拷贝字符
	while (*from != '\0')
	{
    
    
		*to = *from;
		to++;
		from++;
	}
	*to = '\0';
}

int main() {
    
    

	// 源字符串
	char str1[] = "Jerry";
	// 目的字符串
	char str2[32] = {
    
     0 };

	try
	{
    
    
		// 调用字符串拷贝函数
		my_strcpy(str2, str1);

		cout << "拷贝成功 : str2 : " << str2 << endl;
	}
	// catch 分支中可以写 异常变量 , 也可以不写
	// 如果不写 , 则不能访问抛出的 异常对象
	catch (Exception1 e)
	{
    
    
		cout << "出现 Exception1 异常 " << endl;
	}
	catch (Exception2 e)
	{
    
    
		cout << "出现 Exception2 异常 " << endl;
	}
	// 抛出的异常 如果要在 catch 分支中访问
	// 需要调用 拷贝构造函数 将异常对象传递给 catch 分支中的异常变量
	/*catch (Exception3)
	{
		cout << "出现 Exception3 异常 " << endl;
	}*/
	catch (Exception3& e)
	{
    
    
		cout << "出现 Exception3& 异常 " << endl;
	}

	cout << "try-catch 代码块执行完毕" << endl;

	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
};

执行结果 :

Exception3 构造函数
出现 Exception3& 异常
Exception3 析构函数
try-catch 代码块执行完毕
请按任意键继续. . .

在这里插入图片描述





四、C++ 异常处理 - 抛出 自定义类指针类型 异常




1、可以同时拦截 指针类型 和 引用类型


在 try-catch 代码块中 , 可以同时拦截 指针类型 和 引用类型 的 异常 ,

系统会将这两种类型 看做 不同的两种类型 ;

  • 指针类型 和 对象类型 可以同时拦截 ;
  • 指针类型 和 引用类型 可以同时拦截 ;
  • 对象类型 和 引用类型 不可以同时拦截 ;

在下面的代码中 , 同时拦截 指针类型异常 和 引用类型异常 , 编译执行成功 ;

	catch (Exception3& e)
	{
    
    
		cout << "出现 Exception3& 异常 " << endl;
	}
	catch (Exception3* e)
	{
    
    
		cout << "出现 Exception3* 异常 " << endl;
	}

在这里插入图片描述


2、抛出 指针类型异常


如果要抛出 指针类型 的异常 , 必须使用 new 关键字 创建该类型的对象 , 然后将获得的指针 抛出 ;

		Exception3* p = new Exception3();
		throw p;

由于手动分配内存创建了对象 , 一定要在使用完毕后 , 释放该对象 ; 否则就会造成内存泄漏 ;

	catch (Exception3* e)
	{
    
    
		cout << "出现 Exception3* 异常 " << endl;

		// 一定要析构函数
		delete e;
	}

代码示例 :

#include "iostream"
using namespace std;

// 异常类
class Exception1 {
    
    };
class Exception2 {
    
    };
class Exception3 {
    
    
public:
	Exception3() {
    
    
		cout << "Exception3 构造函数" << endl;
	}
	Exception3(const Exception3& obj) {
    
    
		cout << "Exception3 拷贝构造函数" << endl;
	}
	~Exception3() {
    
    
		cout << "Exception3 析构函数" << endl;
	}
};

// 拷贝函数 
// 使用 throw 关键字抛出 对象异常
void my_strcpy(char* to, char* from) {
    
    
	if (from == NULL)
	{
    
    
		// 源字符串出错
		throw Exception1();
	}
	if (to == NULL)
	{
    
    
		cout << "抛出 Exception3 对象 异常" << endl;
		// 目标字符串出错
		throw Exception2();
	}

	// 拷贝前检查条件
	// 源字符串不能是 J 开头的
	if (*from == 'J')
	{
    
    
		// 这些写会导致野指针出现
		// 该行代码执行完成后, 出了作用域该对象会被析构
		// 在 catch 分支中获取到的指针是野指针
		//throw &(Exception3());

		Exception3* p = new Exception3();
		throw p;
	}

	// 逐个字节拷贝字符
	while (*from != '\0')
	{
    
    
		*to = *from;
		to++;
		from++;
	}
	*to = '\0';
}

int main() {
    
    

	// 源字符串
	char str1[] = "Jerry";
	// 目的字符串
	char str2[32] = {
    
     0 };

	try
	{
    
    
		// 调用字符串拷贝函数
		my_strcpy(str2, str1);

		cout << "拷贝成功 : str2 : " << str2 << endl;
	}
	// catch 分支中可以写 异常变量 , 也可以不写
	// 如果不写 , 则不能访问抛出的 异常对象
	catch (Exception1 e)
	{
    
    
		cout << "出现 Exception1 异常 " << endl;
	}
	catch (Exception2 e)
	{
    
    
		cout << "出现 Exception2 异常 " << endl;
	}
	// 抛出的异常 如果要在 catch 分支中访问
	// 需要调用 拷贝构造函数 将异常对象传递给 catch 分支中的异常变量
	/*catch (Exception3)
	{
		cout << "出现 Exception3 异常 " << endl;
	}*/
	catch (Exception3& e)
	{
    
    
		cout << "出现 Exception3& 异常 " << endl;
	}
	catch (Exception3* e)
	{
    
    
		cout << "出现 Exception3* 异常 " << endl;

		// 一定要析构函数
		delete e;
	}

	cout << "try-catch 代码块执行完毕" << endl;

	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
};

执行结果 :

Exception3 构造函数
出现 Exception3* 异常
Exception3 析构函数
try-catch 代码块执行完毕
请按任意键继续. . .

在这里插入图片描述


3、错误示例


错误示例 : 如果 使用 throw &(Exception3()); 方式抛出异常 , 该行代码执行完成后, 出了作用域该对象会被析构 , 在 catch 分支中获取到的指针是野指针 ;

在这里插入图片描述

代码示例 :

#include "iostream"
using namespace std;

// 异常类
class Exception1 {
    
    };
class Exception2 {
    
    };
class Exception3 {
    
    
public:
	Exception3() {
    
    
		cout << "Exception3 构造函数" << endl;
	}
	Exception3(const Exception3& obj) {
    
    
		cout << "Exception3 拷贝构造函数" << endl;
	}
	~Exception3() {
    
    
		cout << "Exception3 析构函数" << endl;
	}
};

// 拷贝函数 
// 使用 throw 关键字抛出 对象异常
void my_strcpy(char* to, char* from) {
    
    
	if (from == NULL)
	{
    
    
		// 源字符串出错
		throw Exception1();
	}
	if (to == NULL)
	{
    
    
		cout << "抛出 Exception3 对象 异常" << endl;
		// 目标字符串出错
		throw Exception2();
	}

	// 拷贝前检查条件
	// 源字符串不能是 J 开头的
	if (*from == 'J')
	{
    
    
		// 这些写会导致野指针出现
		// 该行代码执行完成后, 出了作用域该对象会被析构
		// 在 catch 分支中获取到的指针是野指针
		throw &(Exception3());

		/*Exception3* p = new Exception3();
		throw p;*/
	}

	// 逐个字节拷贝字符
	while (*from != '\0')
	{
    
    
		*to = *from;
		to++;
		from++;
	}
	*to = '\0';
}

int main() {
    
    

	// 源字符串
	char str1[] = "Jerry";
	// 目的字符串
	char str2[32] = {
    
     0 };

	try
	{
    
    
		// 调用字符串拷贝函数
		my_strcpy(str2, str1);

		cout << "拷贝成功 : str2 : " << str2 << endl;
	}
	// catch 分支中可以写 异常变量 , 也可以不写
	// 如果不写 , 则不能访问抛出的 异常对象
	catch (Exception1 e)
	{
    
    
		cout << "出现 Exception1 异常 " << endl;
	}
	catch (Exception2 e)
	{
    
    
		cout << "出现 Exception2 异常 " << endl;
	}
	// 抛出的异常 如果要在 catch 分支中访问
	// 需要调用 拷贝构造函数 将异常对象传递给 catch 分支中的异常变量
	/*catch (Exception3)
	{
		cout << "出现 Exception3 异常 " << endl;
	}*/
	catch (Exception3& e)
	{
    
    
		cout << "出现 Exception3& 异常 " << endl;
	}
	catch (Exception3* e)
	{
    
    
		cout << "出现 Exception3* 异常 " << endl;

		// 一定要析构函数
		delete e;
	}

	cout << "try-catch 代码块执行完毕" << endl;

	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
};

执行结果 :

Exception3 构造函数
Exception3 析构函数
出现 Exception3* 异常
try-catch 代码块执行完毕
请按任意键继续. . .

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/han1202012/article/details/134742818
今日推荐