C++:何时使用"new",何时不使用? |添加:不重复

C++: When to use "new" and when not? | Add: Not duplicated

本文关键字:添加 何时不 何时使 new C++      更新时间:2023-10-16

我是C++的新手,想知道什么时候应该使用new,什么时候不应该,例如"int x=5"或"int*x=new int(5)"。我知道new在堆中保留内存,因此在块结束时不会被删除,但由于保存地址的变量在块之外将变得不可访问,我看不到任何好处。

示例:

if(x) {
 int * z = new int(5);
 // Do something
}
// At this point the 5 is saved somewhere but since z is unaccessible I can't use it.

补充:这个问题没有重复,因为其他问题只解释了什么是堆,而没有描述它的好处。

当您:时使用堆分配

  • 希望对象超过创建对象的范围的末尾
  • 需要一个在生成时大小未知的数组(内存块)
  • 创建的对象或内存块太大,会导致此网站的同名(堆栈溢出)

考虑这样一种情况:函数make创建一个对象并返回一个指向该对象的指针。如果在堆栈上分配该对象,则在函数结束后该对象将不存在。分配在堆上,它一直存在,直到通过调用delete释放内存,因此调用者可以安全地使用在make中创建的对象。

(我在这里松散地使用了"安全"一词,因为释放内存的责任现在落在了与分配内存的实体不同的实体上。如果内存的新所有者忽略了释放内存,就会发生内存泄漏。解决方法是使用智能指针来管理动态分配的内存,特别是unique_ptr)。

想象一下,你正穿过城镇,在许多不同的建筑里有许多不同的工作要做。你有一个袋子,里面可以装笔记。每栋楼都有一个空记事本,你可以使用,但你不能把它带出楼。

这可能是因为你需要在工作间隙随身携带一些笔记。这些必须写下来,放在你的包里,包里会带着你在城里的一份又一份工作。当你不断添加笔记时,袋子会变得更重,放入和取出笔记也需要时间。(你只能简单地从袋子里拿出一张纸条来查阅。)你可以随时丢弃其中一张纸条,但这也需要一点时间(然后它就永远消失了)。

相比之下,有些注释仅与当地工作/建筑相关。对于这些,最好使用大楼自己的本地记事本。当你离开大楼时,你不能随身携带这些笔记(当你离开时,记事本实际上被扔进了垃圾桶),但从大楼的记事本中添加和阅读笔记比在随身携带的包中使用笔记更快、更容易(从包中取出这些笔记总是比查阅当地记事本花费更多的时间)。

有时你会发现你想把一些本地笔记(在记事本上)复制到包里的一套永久笔记中,因为这些信息是你以后工作所需要的。有时,当你第一次进入大楼时,把一些永久性的笔记复制到当地的建筑记事本上会很有效,因为如果你要在那里呆一段时间,经常查阅这些笔记,实际上,把它们放在易读的记事本上比一直从包里拿出来更容易(记住,每次读它们时你都必须把它们从包里搬出来)。

那袋永久性的钞票就是一堆。用它做你想在工作间隙随身携带的东西,但如果你能很容易地使用本地记事本,就不要用它。本地记事本使用起来更快,而且不会占用你包里的空间。但这也意味着你不能用它来获取工作间隙需要的信息,除非你把这些信息复制到你的笔记袋里。本地记事本就是堆栈。

这实际上有一些复杂之处:你不知道,你的永久性袋子里面实际上有巧妙的层,这意味着当你从袋子深处取出一张纸条时,它会在离袋子顶部更近的地方停留一段时间,这样下次取回所需的时间就会更短。但你不能指望这种机制:一般来说,你应该假设从包里拿出笔记是很昂贵的。

C++堆的主要特征是,使用new分配的任何内存都将保持分配状态,直到使用delete解除分配,并且堆可以用于大型数据结构的大量内存分配。

使用new分配内存的缺点是,现在必须管理内存分配,因为C++标准目前没有垃圾回收的规定(C++17)。因此,如果您使用new分配内存,您有责任确保在某个时刻调用delete,否则您可能会出现内存泄漏,应用程序使用的内存量会越来越大。

一旦创建变量的作用域退出,C++堆栈上的局部变量将自动删除。然而,由于C++堆栈的可用内存通常比堆少得多,因此不应将其用于大量内存分配。

无论z是指针还是z不是指针,只要需要,都可以使用相同的机制、过程和编码来使变量z和该变量的值可用。不同之处在于,如果使用指针,则必须在指针超出范围之前释放它所指向的内存,或者必须保存指针值,以便以后可以delete分配的内存。

请注意,当指向内存区域的基本指针(如int *z)超出范围时,它将被消除,就像任何其他变量一样。但是,除非在指针变量不再可访问之前执行显式delete,否则它所指向的内存不会被释放。

在C++堆栈上使用变量提供了一些好处。每次调用函数时,都会自动分配内存区域,并在函数退出时自动解除分配。使用同一函数的多个线程将各自具有自己的堆栈空间,并且它们对该函数的使用将导致每个线程具有自己的一组堆栈分配变量。使用堆栈还允许重入代码和递归代码,这样处理中断和调用自身的函数的问题就可以很好地解决。

有关递归和可重入性的信息,请参阅以下文章。

了解递归函数如何工作

什么是可重入函数?

C,C++中可重入代码的推荐做法

然而,我们遇到了数据结构大小的问题,一些数据结构需要有一个不依赖于特定块或功能的生命周期。这就是使用new进行堆分配的有用之处和实际必要之处。

有几种不同的方法可以使用new和堆分配。最好的方法是让编译器通过使用智能指针来计算它。当智能指针变量超出范围时,智能指针将释放分配的内存,作为其销毁处理的一部分。

例如,请参阅:

资源获取是初始化(RAII)是什么意思?

C++中的RAII和智能指针

关于你的观察,分配的内存将变成";不可访问";当包含地址的指针超出范围时。您是正确的,您有责任确保在包含地址的指针超出范围之前使用delete释放内存。这正是智能指针被发明用来解决的问题。另一点是,在以下两个例子中,访问变量没有区别:

if (x) {
    int *z = new(5);
    // .. do things with z or *z
    delete z;
}
// variable z is no longer available

if (x) {
    int z = 5;
    // .. do things with z
}
// z is no longer available

在这两种情况下,变量z都超出了范围,不能再使用。使用这两个不同版本的z之间的唯一区别是,您必须取消引用指针才能访问第一个版本和第二个版本中指向的值。