【C++详解】:栈解旋以及异常变量的生命周期

141 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第31天,点击查看活动详情

1️⃣前言

今天的笔记内容是:栈解旋与异常变量的生命周期


2️⃣栈解旋

🔋理解

异常被抛出后,从进入try块起到异常被抛掷前,这期间在栈上构造的所有对象都会被自动析构。析构的顺序和构造的顺序相反。这一过程被称为栈的解旋。

通俗来讲就是在try块中,当发生了异常并被抛出时,异常抛出前创建的对象都会被自动析构,不然的话创建的对象会一直存在,占用着内存。

🔋示例

看下代码会更加清楚

#include <iostream>
#include <string> 
using namespace std;
class myClass{
public:
	myClass(string name) {
		m_name = name;
		cout<< m_name << "对象被创建了!" << endl; 
	}
	~myClass() {
		cout<< m_name << "对象被析构了!" << endl; 
	}
	string m_name;	
};
void func1(){
	myClass b("b");
	myClass c("c");
	throw -1; // 函数1抛出异常 
}
void func2() {
	myClass a("a");
	func1(); // 调用函数1
}
int main()
{
	try {
        func2(); // 调用函数2 
    }
	catch(int) {
        cout << "这里是异常处理!" << endl;
    }
	return 0;
}

运行结果为:

image.png

3️⃣异常变量的生命周期

先来看一段代码:

#include <iostream>
#include <string> 
using namespace std;
class myException{
public:
	myException() {
		cout<< "构造函数调用了!" << endl; 
	}
	myException(const myException& e) {
		cout<< "拷贝构造函数调用了" << endl;
	}
	~myException() {
		cout<< "析构函数调用了!" << endl; 
	}
};

void func1(){
	throw myException(); // 抛出异常匿名对象 
}

void func2() {
	try{
		func1();
	}
	catch(myException myExcept){
		cout << "异常捕获并处理!" << endl; 
	}
}
int main()
{
	func2(); 
	return 0;
}

运行结果如下:

image.png

分析如下:

  1. 主函数中调用了函数func2(),执行流程就跳到func2()的函数体中;
  2. func2()中有try-catch语句块,其中try语句调用了func1()函数,所以执行流程又跳到了func1()的函数体中;
  3. func1()中抛出了自定义的异常类对象myException()此时构造函数调用了
  4. 异常抛出后,执行流程就跳到 func2()中的catch语句块中,异常被catch捕获了;
  5. 此时调用了拷贝构造函数,将匿名对象myException()拷贝给了myExcept;
  6. 接着是异常的处理(在上述代码中是输出语句的执行)
  7. 最后是栈解旋,即析构函数的自动调用,且析构的顺序和构造的顺序相反。

如果,在上述代码中,将catch中的捕获语句改为引用类型,如下所示:

void func2() {
	try{
		func1();
	}
	catch(myException& myExcept){
		cout << "异常捕获并处理!" << endl; 
	}
}

则代码运行的结果如下:

image.png

也就是说没有调用拷贝构造函数。直接将抛出的异常匿名对象赋值给引用对象,也就是起别名。此时抛出的异常对象的生命周期就发生了变化。即生命周期交给引用对象所托管(这种效率会较高些)。

4️⃣写在最后

好了,本篇笔记就到写这,欢迎大家到评论区一起讨论!