在C++中,我该如何选择堆分配和栈分配?

20

C++的一个特点是可以将复杂对象作为成员变量或局部变量进行分配,而不总是必须使用 new 进行分配。但这也引出了在任何给定情况下选择哪种分配方式的问题。

是否有一些好的标准来选择如何分配变量?应该在什么情况下声明成员变量为普通变量而不是引用或指针?在什么情况下应该使用 new 分配变量,而不是使用在堆栈上分配的局部变量?


5
你了解值和指针有不同的语义吗?这应该是你的第一个决策点。 - R. Martinho Fernandes
1
@R.MartinhoFernandes: 我对此非常了解。但是在几乎所有可以使用值语义的地方,你也可以使用指针语义。毕竟,指针只是一个值而已。 - Omnifarious
2个回答

24
其中一个让C++与其他语言不同的特点是你必须手动进行内存分配。但我们暂且不谈这个问题。
以下是一些内存分配的建议:
  • 当一个对象的生命周期必须超出某个范围,而且复制或移动代价高昂或不可能时,请在堆上进行分配。
  • 当一个对象很大(如果你想保险起见,这里的“很大”可能意味着几千字节),以防止栈溢出,即使该对象只是临时需要时,请在堆上进行分配。
  • 如果你正在使用pimpl(编译器防火墙)惯用法, 请在堆上进行分配。
  • 对于可变大小的数组,请在堆上进行分配。
  • 否则,请在栈上进行分配,因为它更加方便。
请注意,在第二条规则中,“大型对象”指的是类似于:
char buffer[1024 * 1024];  // 1MB buffer

但不是

std::vector<char> buffer(1024 * 1024);

由于第二个实际上是一个非常小的对象,它包装了指向堆分配缓冲区的指针。

至于指针与值成员:

  • 如果需要堆分配,则使用指针。
  • 如果要共享结构,请使用指针。
  • 对于多态性,请使用指针或引用。
  • 如果从客户端代码获取对象并且客户端承诺保持其活动状态,请使用引用。
  • 在大多数其他情况下,请使用值。

当然,在适当的情况下建议使用智能指针。请注意,在堆分配的情况下可以使用引用,因为您始终可以delete &ref,但我不建议这样做。引用是伪装成指针的指针,只有一个区别(引用不能为null),但它们也传达了不同的意图。


4
+1,但我认为我们应该在你的第一点中提到RVO。这种情况很多时候同时适用于你所说的两点,仍然可以在栈上分配(例如某些返回对象的帮助函数)。 - Voo
+1 你可能还可以添加多态上下文,其中具体类型在编译时无法确定。虽然多态性并不一定意味着动态分配。 - Christian Rau
这并不涉及成员变量应该是指针还是按值包含的问题。而且还有像pimpl惯用法这样的事情需要考虑。 - Omnifarious
@Omnifarious:添加了一些关于何时使用指针成员的提示。 - Fred Foo

2
larsmans的回答已经很详细了,没什么可补充的。 在堆栈上分配通常可以简化资源管理,您不必担心内存泄漏或所有权等问题。一个GUI库就是基于这个观察建立的,可以查看“一切都属于某个地方”和“谁拥有小部件”。 如果您在堆栈上分配所有成员,则默认的复制构造函数和默认的op=通常就足够了。如果您在堆上分配成员,则必须小心实现它们。 如果您在堆栈上分配成员变量,则必须可见该成员的定义。如果您将其分配到堆上,则可以前向声明该成员。我个人喜欢前向声明,因为它可以减少依赖关系。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接