问题 什么时候应该在堆栈而不是堆上分配一个类


在过去,每当我需要创建一个类的实例时,我都会使用new在堆上分配它(除了stl类和数学类,如vec3和mat4)。

但是,我只是批评我的一些代码,并意识到技术上我可以只是在堆栈上制作这些类。它们不是很大,不需要在当前范围之外进行修改等。当我(偶尔)需要将它们传递给另一个函数时,我可以像使用指针一样轻松地使用引用。

在过去,我总是默认在堆上分配,并且在某些情况下只使用堆栈,但是现在我想知道默认情况下在堆栈上分配是否更好,并且只在使用堆时

  • 确实需要一个指针(即对象的生命周期超出声明范围)
  • 类或数组对于堆栈来说太大了
  • 继承需要它(抽象基类/接口)
  • 别的什么?

这也引出了一个问题:一个类有多大(大致)在堆栈上合理分配? (假设我们正在努力,至少是智能手机,并进入高端桌面)我是否只是担心不必要的堆栈大小限制? (可能,只要我们不讨论大型数组,并且没有类甚至接近千字节)


4507
2017-09-08 17:44


起源

当你希望对象的生命周期超出声明范围时,我会补充一下。在你的列表中,除了可能的类大小参数之外,我将删除所有。 - juanchopanza
不是直接的答案,但堆栈分配确实比堆分配具有一些真正的优势(例如速度,自动范围等) 这个答案 列举了其中的一些。 - Miguel
@juanchopanza我认为这是'确实需要一个指针'的情况之一 - zacaj
“一个类有多大(大致)在堆栈上合理分配” - 这完全取决于实现。对于某些实现(微控制器等),4k的堆栈是巨大的。对于其他人来说,这绝对是微不足道的。该标准没有提供便携式代码的方法来确定它是什么,并且当你使用太多堆栈时的行为是程序被毫不客气地终止 如果你很幸运。您只需决定是否编写“低堆栈友好”的代码,然后抓住机会。 - Steve Jessop
@stevejessop我正在开发一款游戏引擎,所以我想成像智能手机级别的基于arm6 / 7的系统将在低端堆栈尺寸方面 - zacaj


答案:


我更喜欢在堆栈上分配,原因有两个。首先,在其他条件相同的情况下,它比堆快。此外,解除分配自动发生,我不需要记住 delete 它(当然,有 auto_ptrs等帮助。)

确实需要一个指针

将指针传递给堆栈上的对象是可以的。只需确保该指针的用户在其生命周期到期后不访问该对象。

类或数组对于堆栈来说太大了

只有真正重要的事情应该重要。你可能有1MB的堆栈,所以在出现问题之前你可以放置大约1000个1KB的对象。

继承需要它

为什么会这样?

别的什么?

对象所需的生命周期长于堆栈帧的生命周期。这是在堆上分配的主要原因。


4
2017-09-08 17:52



“为什么会这样?” - 根据参数和/或某些配置返回差异具体类的工厂函数。不是 究竟 需要它的继承,事实是用户只知道基类。 - Steve Jessop
@SteveJessop:在这种情况下,我只想说API需要它(所有构造函数,如果有的话,都是私有的)。它与继承无关。 - Keith Randall
@stevejessop这几乎就是我的意思。我有很多抽象基类,它们有许多不同的实现,在这种情况下需要一个指针 - zacaj


您的默认行为应该是:

如果对象的生命周期与特定范围一致
即在编译时很容易确定

那么它应该是一个自动存储持续时间对象(像堆栈一样)

如果对象的生命周期是在运行时定义的并且超出了当前范围

那么它应该是一个动态存储持续时间对象(堆像)

注意:所有动态存储持续时间对象应通过将它们包装在适当的RAII类中来控制其生命周期。通常这意味着:对于单个对象,智能指针,而多个对象最终在容器中。

一世 讨厌 看东西定义为 堆栈与堆。因为它没有传达情况的真实语义。

 int x;       // its on the stack
 struct X
 {
     int x;   // Is this a stack or heap object?
 }            // It depends on its parent.


 // But in both cases it is an automatic storage duration object.
 // In both cases the lifespan's are well defined.
 //     The first is defined by the scope it is declared within.
 //     The second is defined by the lifespan of its parent.

您应该考虑自动/动态“存储持续时间”对象。这传达了语言的正确语义。

注意,还有另外两种类型的变量,从而产生四种不同类型的变量。自动/动态/静态/线程'存储持续时间'对象。


7
2017-09-08 18:03



现在使用智能点几乎是所有动态分配的行业标准吗? - zacaj
@zacaj:你会希望如此(但不要陷入陷阱,智能指针是唯一的机制(想想容器))。对于那些写真正的现代C ++的人来说。但是仍然有很多人用C ++编译器编写C语言(AKA用C语言编写类)。但是所有好的异常安全C ++代码都是使用智能指针编写的。 - Martin York


何时应该在堆栈而不是堆上分配类?

只要有可能,不会给您带来很大的不便。会有例外,但作为一般规则回答您的问题:创建实例时, new/new[] 应该 类型 不到百分之一的时间。


确实需要一个指针(即对象的生命周期超出声明范围)

是的,在适当的情况下。从你对OP中描述的堆的使用来判断,这可能比你想象的要少得多。

类或数组对于堆栈来说太大了

是,  这应该不是什么大问题 - 一个很大的课程 一般 表明你的班级出现了根本性的错误。客户友好类可能会考虑在堆上创建那些巨大的,固定大小的数组。

继承需要它(抽象基类/接口)

在某些情况下(例如,存在抽象工厂或多态类型的深层克隆),但它是  创建类型,问题通常会从您的程序使用堆栈之前转移,然后才能考虑它。

别的什么?

没有


原因:

  • 它清晰,简洁,其寿命和范围都很明确。
  • 减少资源消耗。
  • 更少内存相关的错误或可能出错的事情。
  • 速度。堆栈分配非常快。更少的锁。

1
2017-09-08 18:36





只有在需要动态分配内存时才在堆上进行分配。这意味着您不知道在编译时需要分配多少。 您在所有其他时间在堆栈上分配


-2
2017-09-08 17:48



-1表示“意味着您不知道在编译时需要分配多少”。这是错误的。当你需要在堆上分配时,OP提供了一些其他情况,例如分配对于堆栈来说太大的东西。您可能在编译时知道需要一个1000万个整数的数组,但这对于大多数操作系统上的堆栈来说太大了,因此您应该在堆上分配它。 - Adam Mihalcin
-1这不是一个好的答案 - 有时堆栈根本不够大。 - mathematician1975
您可能需要在系统中传递对象,这意味着您的函数将返回该对象,但它将不再有效。 - roni
另请参阅Keith Randall的最后两句话 - 对象生命周期是一个关键考虑因素。 - Bryan