【问题标题】:why would I forbid allocation in the heap?为什么我要禁止在堆中分配?
【发布时间】:2011-04-01 02:30:52
【问题描述】:

我最近阅读了很多关于“防止类的堆分配”(参见this question)。

我能够理解“如何”,但现在我无法弄清楚“为什么”有人愿意这样做。

我想这一定有正当的理由,但我就是想不通。

简而言之:“为什么我要禁止用户在堆中创建我的类的对象?”

【问题讨论】:

  • 我真的想不出你所指问题的充分理由。使用这个问题的当前措辞,您很可能会得到很多答案:为什么我更喜欢在堆栈而不是堆中分配?,这与 为什么我可以想要禁止用户在堆中创建我的类的对象?
  • @David:根据您的建议修改了措辞。谢谢。
  • 我修改了标题以反映实际问题。
  • 堆分配基本上有两种:直接和间接。如果一个对象是堆分配的另一个对象的子对象(基对象或成员),则该对象是间接堆分配的。这个问题目前没有区分它们,答案也没有解决它。然而,对于间接堆分配的对象,上述根本原因可能成立,也可能不成立。
  • 一般来说,如果这样做总是一个错误(例如,浅复制一个对象以释放其析构函数中的资源),你只想明确禁止某些事情,而不仅仅是因为你可以'想不出允许它的理由。动态分配是否有任何原因可能会出错?

标签: c++ class memory-management heap-memory stack-memory


【解决方案1】:

看起来我会逆流而上(所以我确实希望投反对票,但请发表评论以说明原因)。

我认为没有任何理由禁止堆分配,主要是因为我不喜欢猜测我创建的类的潜在用途。

作为设计规则,我倾向于尽可能少地限制类的使用。这意味着尽可能少的假设。没有什么比仅仅因为它被禁止而无法做你想做的事更令人抓狂的了……原因不明或完全错误(表示图书馆作者的迷信/错误信念)。

此外,实用主义告诉我们实际上阻止 C++ 中的任何事情几乎是不可能的。例如,有些人谈到了守卫——>如果我想创建一个超类(方便地添加日志记录)怎么办?然后我会将保护类作为一个属性,即使它的(原始类)new 运算符是私有的,我的超类也可以在堆上实例化,除非它以某种方式复制机制。

所以,对我来说,这不是为什么或如何的问题。我只是不摆弄库代码中的内存分配方案,由用户决定使用对她来说最方便的方式。

【讨论】:

    【解决方案2】:

    对于某些类型的非常小的对象 - 考虑几何引擎中的 3D 点 - 在堆上单独分配这些点是创建悬空指针并引入大量开销的秘诀 - 但是,它们在许多计算

    因此,一种常见的机制是在实际包含 3D 点集合的对象中使用享元模式,例如对某些几何实体的描述,并允许它们在堆上用作计算的中间结果。

    现在,也就是说,需要特别注意避免在享元实现之间不必要的数据复制,例如ListOf3DPoints 和堆分配的 3D 点用于中间结果。

    一般来说,我发现在很多情况下,我将享元模式与堆分配功能相结合以实现最佳结果 - 享元提供持久存储,堆分配让我无需生成即可执行项目级别操作另一个蝇量级

    【讨论】:

      【解决方案3】:

      堆栈分配更快(无需搜索空间)。

      【讨论】:

      • 是的,但这不是禁止在堆栈上分配特定类的对象的原因。
      【解决方案4】:

      通常最好防止类的意外使用。例如,考虑依赖 RAII 技术的 Guard 类。它们必须在堆栈上分配,并且在它们超出范围时完成它们的工作。没有人期望用户在堆中分配保护对象,因此明确禁止。

      显式优于隐式。 Herb Shutter 说,错误地使用你的类一定很难(在堆中分配),而且很容易正确使用(在堆栈中)。

      【讨论】:

      • 你对 RAII 的看法是对的,但是可以堆分配一个锁守卫实例并将其绑定到 auto_ptr 以达到相同的结果。
      • 但是auto_ptr必须明确分配在栈上。所以,基本上是一样的东西,那为什么我们需要额外的抽象层次呢?
      • @SadSido:auto_ptr 可以嵌入到对象中,或从函数返回,以将其控制的对象的生命周期延长到当前范围之外。我可以想象你想要这样做的场景,如果任意禁止堆分配,就会被挫败。
      • 好点。尽管如此,它一定是一个非常复杂的场景,让您需要延长 RAII Guard 的使用寿命。我想,这种情况必须避免=)
      • @SadSido:创建一个辅助函数,在返回警卫之前检查死锁。这似乎很有用,并且没有移动语义需要堆分配。我真的认为 Sutter 在谈到防止不正确使用时从未想过要谈论内存分配方案。
      【解决方案5】:

      某些类只有在对象在堆栈上实例化时才有意义。例如,Boost scoped_ptrlock_guard

      【讨论】:

      • 差不多这样 - 有些类只有在堆栈上分配时才有意义。
      • 查看boost文档,似乎他们没有重新定义几个new操作符来防止堆分配。它是隐藏的还是他们没有这样做。如果是,为什么?
      • boost::scoped_ptr 对于 pimpl 也有意义,如链接中所述。这不是(必然)在堆栈上。
      • 我可以想象您希望将这两个类的生命周期延长到当前范围之外的情况 - 可能是不寻常的行为,但它肯定是有道理的。你为什么要跳过箍来防止这种情况发生?
      • 特别是考虑到简单地重新定义new 并不会阻止对象嵌入到本身是堆可分配的超类中......
      【解决方案6】:

      主要是因为堆栈分配的对象在超出范围时会自动清理,从而消除了一大类错误——即内存分配错误。

      【讨论】:

      • 为了节省大约一百条 CPU 指令而引入语义噩梦对我来说听起来像是过早优化。
      • @Visage:堆分配的开销远远超过一百条 CPU 指令——如果过度使用,间接到堆的成本可能会很大,此外,管理内存会使用额外的周期和内存.基本事实是,从性能角度来看,堆栈分配更好,并且在语义上也更干净。
      • @Visage:也许您甚至可以提供一个示例,其中堆栈和堆都可以使用并且堆分配比堆栈分配更干净......我认为没有一个示例。现在,考虑:void foo() { for ( int * i = new int(0); *i < 10< ++*i ) { ... } }--ouch,如何释放那里的指针?使用智能指针 --in the stack --: void foo() { for ( auto_ptr<int> i(new int(0)); *i < 10; ++*i ) { ... }... 我们还在使用堆栈,代码更复杂,或者不是?
      • 我不确定在现实生活中永远不会使用的人为例子是否真的支持你的论点。
      • @Visage:问题是堆栈分配总是比代码中的堆分配简单。我提供了第一个例子:循环的控制变量不可能用纯堆分配来编写。现在球在你的地面上:你能提供任何堆分配比堆栈分配更干净的情况的证据吗? (将示例限制在您可以实际使用堆栈分配的情况)。栈默认的,堆是当栈不适合问题的时候。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 2016-06-05
      • 2022-01-07
      • 1970-01-01
      • 2021-08-18
      • 2021-12-20
      • 1970-01-01
      相关资源
      最近更新 更多