何时以及为何在堆 C++ 上声明成员变量
Posted
技术标签:
【中文标题】何时以及为何在堆 C++ 上声明成员变量【英文标题】:When and why to declare member variables on the heap C++ 【发布时间】:2012-07-13 19:59:28 【问题描述】:好的,所以我是 C++ 编程的新手,我已经寻找了几天的决定性答案。我应该什么时候在堆和堆栈上声明成员变量?我发现的大多数答案都涉及其他问题,但我想知道何时最好将堆用于成员变量以及为什么将成员堆而不是堆叠它们更好。
【问题讨论】:
member 变量指的是 class 成员,因此去到 class 的 instance 去哪里。跨度> 【参考方案1】:首先要掌握两个重要概念:
应该避免考虑“堆”和“堆栈”。这些是您的编译器/平台的实现细节,而不是语言。1 相反,请考虑对象生命周期:对象的生命周期是否与其“父对象的生命周期相对应“,或者它应该活得更久?如果需要后者,则需要使用new
(直接或间接)动态分配对象。
成员变量总是与其父变量具有相同的生命周期。成员变量可能是一个指针,它指向的对象很可能有一个独立的生命周期。但是指向的对象不是成员变量。
但是,您的问题没有通用答案。粗略地说,除非有充分的理由,否则不要动态分配。正如我上面所暗示的,这些原因通常对应于生命周期需要与其“父级”不同的情况。
1。事实上,C++ 标准并没有真正谈论“堆”和“栈”。在优化或一般考虑性能时要考虑它们很重要,但从程序功能的角度来看,它们大多无关紧要。
【讨论】:
“通常对应于生命周期需要与其父级不同的情况”:那么我们还能以成员的身份谈论某事吗? @JamesKanze:确实,我应该澄清一下。 对象也需要动态生命周期,如果它们是动态大小的,或者用于将对象转换为虚拟基类。 @MooingDuck:同意动态调整大小(尽管您可能会争辩说这又是一个终生的问题)。虽然不确定基类的意义,尽管这需要指针(或引用),但不一定需要动态分配。 @OliCharlesworth:不,动态大小甚至与生命周期无关。没错,基指针容器不需要 堆分配,但没有它几乎没用。【参考方案2】:成员变量是类本身的成员。他们既不在 堆也不在堆栈上,或者更确切地说,它们在类的任何地方 本身就是。
添加一个间接级别并分配一个 堆上单独的成员:多态性(如果成员的类型 并不总是相同的)是迄今为止最常见的。
【讨论】:
对象也需要动态生命周期,如果它们是动态大小的,或者用于将对象转换为虚拟基类。 @MooingDuck 动态调整大小的对象应该由它们自己的类管理(例如std::vector
):你不要将它们分配为成员;你让容器成为成员。并转换为虚拟基类或任何暗示该成员是多态的,这就是我提到的。
多态不需要堆分配。考虑:导出的 x;基数* y=&x;这里没有进行堆分配。
@Marcin 也不需要多态性。如果一个类的成员要具有多态行为,它几乎总是必须动态分配。 (当然也有例外,但很少见。)
@James Kanze:每个人都说 std::vector 必须用于动态大小的对象。但是你总是忘记那些你有 C++ 编译器但没有 STL 的系统。并非所有 C++ 系统都有 STL。 (查看一些较小的嵌入式环境)。【参考方案3】:
为了弄清楚一些术语:你所谓的heap
和stack
描述了对象的生命周期。第一个意味着生命周期是dynamic
,第二个是automatic
,第三个(你没有提到)是static
。
通常你需要一个对象的dynamic
生命周期,当它应该超过它被创建的范围时。另一个常见的情况是你希望它在不同的父对象之间共享。此外,当您使用高度面向对象的设计(使用大量多态性,不使用值)时,动态生命周期也是必要的,例如Qt
.
一个需要动态生命周期的习语是 pimpl-idiom。
大多数通用编程库更侧重于值和值语义,因此您不会大量使用动态绑定,自动生命周期变得更加普遍。
还有一些例子,由于更具体的实现原因需要动态分配:
动态大小的对象(容器) 处理不完整的类型(参见 pimpl-idiom) 类型的简单可空性所有这些都只是一般指导方针,必须根据具体情况来决定。一般来说,比起动态对象,更喜欢自动对象。
【讨论】:
【参考方案4】:堆栈指的是call stack
。函数调用、返回地址、参数和局部变量都保存在调用堆栈中。每当您传递参数或创建局部变量时,都会使用堆栈内存。堆栈只有临时存储。当前函数超出范围后,您将无法再访问任何参数变量。
堆是一个用于动态分配的大型内存池。当您使用new
运算符分配内存时,此内存是从堆中分配的。当您创建不想在当前函数终止(失去作用域)后丢失的对象时,您希望分配堆内存。对象存储在堆中,直到空间被delete
或free()
释放。
【讨论】:
对象也需要动态生命周期,如果它们是动态大小的,或者用于将对象转换为虚拟基类。【参考方案5】:考虑这个例子:你实现了一个链表,它有一个类节点的字段成员头。
每个节点都有一个字段成员next
。如果此成员属于 Node 类型而不是 Node*,则每个 Node 的大小将取决于链中它之后的节点数量。
例如,如果您的列表中有 100 个节点,那么您的头成员将是巨大的。因为它在自身内部保存下一个节点,所以它需要有足够的大小来保存它,并且 next 保存下一个,依此类推。所以头部必须有足够的空间来容纳 99 个节点,接下来是 98 个,依此类推...
你想避免这种情况,所以在这种情况下,最好在每个节点中都有指向下一个节点的指针,而不是下一个节点本身。
【讨论】:
以上是关于何时以及为何在堆 C++ 上声明成员变量的主要内容,如果未能解决你的问题,请参考以下文章