【问题标题】:When should a class be allocated on the stack instead of the heap什么时候应该在堆栈而不是堆上分配一个类
【发布时间】:2012-09-08 17:44:43
【问题描述】:

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

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

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

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

这也提出了一个问题:一个类有多大(大致)以至于无法在堆栈上合理分配? (假设我们至少在开发智能手机,并升级到高端台式机)我是否只是不必要地担心堆栈大小限制? (可能,只要我们不是在谈论大型数组,并且任何类都不会接近一千字节)

【问题讨论】:

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

标签: c++


【解决方案1】:

您的默认行为应该是:

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

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

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

那么它应该是一个动态的存储时长对象(heap like)

注意:所有动态存储持续时间对象都应通过将它们包装在适当的 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.

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

请注意,还有两种其他类型的变量,因此产生了四种不同类型的变量。自动/动态/静态/线程“存储持续时间”对象。

【讨论】:

  • 现在所有动态分配都使用智能点几乎是行业标准吗?
  • @zacaj:你希望如此(但不要陷入智能指针是唯一机制的陷阱(想想容器))。对于编写真正的现代 C++ 的人来说,是的。但是仍然有很多人使用 C++ 编译器(也称为 C-with 类)编写 C。但是所有好的异常安全 C++ 代码都是使用智能指针编写的。
【解决方案2】:

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

确实需要一个指针

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

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

只有真正重要的事情才有意义。您可能有 1MB 的堆栈,因此您可以在出现问题之前放置大约 1000 个 1KB 的对象。

继承需要它

为什么会这样?

还有什么?

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

【讨论】:

  • “为什么会这样?” - 根据参数和/或某些配置返回不同具体类的工厂函数。这不是确切地需要它的继承,而是用户只知道基类这一事实。
  • @SteveJessop:在这种情况下,我只想说 API 需要它(所有构造函数,如果有的话,都是私有的)。它与继承无关。
  • @stevejessop 这就是我的意思。我有很多抽象基类,它们有很多不同的实现,在这种情况下需要一个指针
【解决方案3】:

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

尽可能且不会造成很大的不便。会有例外,但作为一般规则回答您的问题:创建实例时,new/new[]键入不到百分之一的时间。


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

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

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

是的,但是这应该不是什么大问题——一个很大的类通常表明你的类存在根本性的问题。客户端友好的类可能会考虑在堆上创建那些巨大的、固定大小的数组。

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

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

还有什么?

没有


原因:

  • 它清晰、简洁,并且它的生命周期和范围已经确定。
  • 资源消耗更少。
  • 更少的内存相关错误或可能出错的事情。
  • 速度。堆栈分配非常快。更少的锁。

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • -1 表示“意味着您不知道在编译时需要分配多少。”这是错误的。当您需要在堆上分配时,OP 提供了一些其他情况,例如分配对于堆栈来说太大的东西。您可能在编译时知道您需要一个包含 1000 万个 int 的数组,但这对于大多数操作系统上的堆栈来说太大了,因此您应该在堆上分配它。
    • -1 这不是一个好的答案 - 有时堆栈根本不够大。
    • 您可能需要在系统中传递对象,这意味着您的函数将返回该对象,但不再有效。
    • 另见 Keith Randall 回答的最后两句话 - 对象生命周期是一个关键考虑因素。
    猜你喜欢
    • 2011-03-10
    • 1970-01-01
    • 2014-05-13
    • 1970-01-01
    • 2019-09-16
    • 2010-12-05
    • 2012-08-13
    • 2016-10-15
    相关资源
    最近更新 更多