Cherno C++学习笔记 P43 对象生存周期

这篇文章我们讲一下对象的生存周期。这个也是一个很重要的问题,因为我们总说,编程其实就是在操纵内存,而知道了变量如何在栈上生存,以及我们如何利用这些特性来让我们的编程更加简单,我们才是真的理解了编程。我们都知道,在为变量分配内存的时候,我们可以选择栈上分配,也可以选择堆上分配,由于在堆上分配需要我们手动清理内存,所以相当于我们可以自己决定这个变量的生存时间,因此我们主要了解栈上对象的生存周期。

如何理解栈?我们可以认为栈就像一个书堆,我们新来的书要放在上面,但是如果我们想要取出一本书,也要从上方取走。在栈上创建变量的时候,我们相当于把这个变量的作用域一整个当成一本书,放在了这个书堆的上方;离开了作用域的时候,相当于我们取走了这本书,那么书里面的变量也连带着一起被取走了,自然也就相当于里面的变量消失了。

这种变量会随着作用域的结束而一起销毁的事实,是好事也是坏事,坏处在于我们有的时候忘记了这件事情,想要使用已经结束的作用域中的变量,或者是使用其指针/引用,会发现出错;好处是我们可以利用这样一个特性,让我们在堆上建立变量的时候,不需要delete也可以实现自动销毁。

那么什么叫做作用域?我们的if while循环体,空大括号,函数定义,类定义等都是作用域。我们可以看一个简单的例子展示一下离开作用域之后为什么就会销毁变量,以一个Entity类为例:

cpp 复制代码
#include<iostream>
#include<string>

class Entity {
public:
	Entity() {
		std::cout << "New Entity is Constructed!" << std::endl;
	}
	~Entity() {
		std::cout << "Old Entity is Destroyed!" << std::endl;
	}
};

int main() {
	{
		Entity e;
	}
	std::cin.get();
}

然后我们进入调试模式,可以看到当我们离开作用域了的时候,如下所示:

此时我们已经完成了构建与析构这个实例:

这样我们就可以看到,在作用域结束的时候,我们的对象就已经被摧毁了。

但是如果我们在堆上建立这个变量,比如中间作用域内我们改成这个样子:

cpp 复制代码
{
	Entity* e = new Entity;
}

我们再调试可以发现,在我们离开作用域之后,析构函数依然没有被调用,直到整个程序结束,我们才会调用析构函数来销毁这个变量。

正是因为栈建立的变量有这样的特点,所以如果我们在一个函数中构建变量并返回,那么如果是栈上的变量,就会给我们报错:

cpp 复制代码
static int* Function() {
	int a[50];
	return a;
}

比如像这样的代码,如果我们离开了函数,这个数组其实就已经消失了,我们再返回这个数组的指针也就毫无意义了。不过需要注意的是,如果只是返回值,那么我们用一个新变量来承接这个值,那是没有问题的,但是返回指针通常是有问题的,因为指针指向的那个内容已经随着离开作用域狗带了,要这个指针没有任何意义了。

那么我们如何避免这种情况的发生?整体上有两种方法,第一个是我们在函数内用new关键字在堆上分配内存,这样我们只要不手动清理,这个内存就会一直被分配;第二个是我们可以在外面写一个全局变量,然后把值赋给这个全局变量并返回,这样也可以保证变量一直存在。

但是如果我们善于利用栈上的变量离开了作用域就会被销毁这个特点,其实我们是可以让我们的代码更加简洁的,其中一个重要的作用就是自动化new&delete,可以有效防止我们经常忘记写delete导致内存泄漏的问题。其实这就是最常用的智能指针,作用域指针的设计思路。我们可以自己写这样一个指针类来看看会发生什么:

cpp 复制代码
class ScopedPtr {
public:
	Entity* ptr;
public:
	ScopedPtr(Entity* ptr) {
		this->ptr = ptr;
	}
	~ScopedPtr() {
		delete ptr;
	}
};

我们可以看到,这样一个指针类当中,我们在析构函数当中选择使用delete关键字来释放内存,这样的好处就是,当我们ScopedPtr类型的变量被销毁时,连带着也会释放内存,我们来看一下实际的应用情况:

cpp 复制代码
{
	ScopedPtr p = new Entity;
}

我们可以发现,它的定义方式和常规的new非常类似,但是当我们离开作用域之后,就会发现new分配的内存已经被释放。因为虽然new Entity是在堆上分配内存,但是ScopedPtr类是在栈上分配的内存,在离开作用域之后,它就会调用自己的析构函数,而它的析构函数,我们自己写的,刚刚好又是释放new Entity所分配的内存,所以我们可以不用再额外去写delete,就实现了离开作用域之后自动释放new分配的内存。

以上其实就是智能指针所做的事情,我们后面还会详细讲解智能指针,希望大家喜欢!

相关推荐
依旧阳光的老码农8 分钟前
log4cpp 编译说明文档
c++
草莓熊Lotso10 分钟前
【C语言操作符详解(一)】--进制转换,原反补码,移位操作符,位操作符,逗号表达式,下标访问及函数调用操作符
c语言·经验分享·笔记
OpenC++14 分钟前
【C++QT】Layout 布局管理控件详解
c++·经验分享·qt·leetcode
灏瀚星空32 分钟前
从基础到实战的量化交易全流程学习:1.3 数学与统计学基础——概率与统计基础 | 基础概念
笔记·python·学习·金融·概率论
一夜沐白36 分钟前
Linux用户管理
linux·运维·服务器·笔记
无敌的牛1 小时前
AVL树的介绍与学习
数据结构·学习
1白天的黑夜11 小时前
贪心算法-860.柠檬水找零-力扣(LeetCode)
c++·算法·leetcode·贪心算法
BS_Li1 小时前
C++类和对象(上)
开发语言·c++·类和对象
【0931】1 小时前
进程控制的学习
学习·操作系统
阿图灵1 小时前
文章记单词 | 第48篇(六级)
学习·学习方法