当使用std::list<std::string>时,我偶然发现了堆栈溢出问题std::string的内存泄漏,其中一个注释说:

不要用那么多new。我看不出你为什么在任何地方都用新的。你可以在c++中通过值创建对象,这是使用该语言的一个巨大优势。您不必分配堆上的所有内容。不要像Java程序员那样思考。

我不太确定他这么说是什么意思。

为什么在c++中应该尽可能多地根据值创建对象,这在内部有什么不同?我误解了答案吗?


当前回答

因为堆栈速度更快,而且防漏

在c++中,只需要一条指令就可以为给定函数中的每个局部作用域对象在堆栈上分配空间,而且不可能泄漏任何内存。该评论意图(或应该意图)说“使用堆栈而不是堆”之类的话。

其他回答

我发现有几个重要的原因让我们忽略了尽量少做新事情:

Operator new的执行时间不确定

调用new可能会导致操作系统为进程分配新的物理页,也可能不会。如果你经常这样做,会很慢。或者它可能已经准备好了一个合适的内存位置;我们不知道。如果你的程序需要具有一致且可预测的执行时间(如在实时系统或游戏/物理模拟中),你需要避免在时间关键型循环中添加新的元素。

操作符new是一个隐式线程同步

是的,你听到了。你的操作系统需要确保你的页表是一致的,因此调用new会导致你的线程获得一个隐式互斥锁。如果你一直从许多线程调用new,你实际上是在序列化你的线程(我用32个cpu做过这个,每个cpu都调用new来获得几百个字节,哎呦!那是一个需要调试的皇家p.i.t.a.。)

其余的,比如速度慢、碎片化、容易出错等,其他答案已经提到了。

由new创建的对象必须最终删除,以免泄漏。析构函数不会被调用,内存不会被释放,整个比特。由于c++没有垃圾收集,这是一个问题。

由值创建的对象(即在堆栈上)在超出作用域时自动死亡。析构函数调用由编译器插入,并且在函数返回时自动释放内存。

像unique_ptr、shared_ptr这样的智能指针解决了悬空引用问题,但它们需要编码规则,并有其他潜在的问题(可复制性、引用循环等)。

此外,在大量多线程的场景中,new是线程之间的争用点;过度使用new可能会影响性能。堆栈对象的创建根据定义是线程本地的,因为每个线程都有自己的堆栈。

值对象的缺点是,一旦宿主函数返回,它们就会死亡——你不能将它们的引用传递给调用者,只能通过复制、返回或按值移动。

避免过度使用堆的一个值得注意的原因是为了性能——特别是涉及c++使用的默认内存管理机制的性能。虽然在简单的情况下分配可以非常快,但是在没有严格顺序的情况下对大小不一致的对象执行大量的新建和删除操作不仅会导致内存碎片,而且还会使分配算法复杂化,并且在某些情况下绝对会破坏性能。

这就是创建内存池要解决的问题,可以减轻传统堆实现的固有缺点,同时仍然允许您在必要时使用堆。

不过,最好还是完全避免这个问题。如果可以将它放到堆栈中,那么就这样做。

New在堆上分配对象。否则,对象分配在堆栈上。查一下两者的区别。

许多答案都涉及到各种性能考虑因素。我想解决让OP困惑的评论:

不要像Java程序员那样思考。

事实上,在Java中,正如这个问题的答案所解释的那样,

第一次显式创建对象时使用new关键字。

但在c++中,类型为T的对象是这样创建的:T{}(或T{ctor_argument1,ctor_arg2}对于带参数的构造函数)。这就是为什么通常你没有理由想要使用new。

那么,为什么要用它呢?有两个原因:

您需要创建许多值,这些值的数量在编译时是未知的。 由于c++实现在普通机器上的限制-通过分配太多空间来防止堆栈溢出,以常规方式创建值。

现在,除了你引用的评论暗示的内容之外,你应该注意到,即使是上面的两种情况也已经涵盖得很好了,而不必“求助”使用新的自己:

您可以使用来自标准库的容器类型,它们可以保存运行时可变数量的元素(如std::vector)。 您可以使用智能指针,它为您提供一个类似于new的指针,但确保在“指针”超出作用域的地方释放内存。

因此,在c++社区编码指南中,避免显式的new和delete是一个正式的条款:指南R.11。