我是C ++的新手,我想知道为什么我甚至不愿意使用新的和删除?它可能会导致问题(内存泄漏),我不明白为什么我不应该在没有new运算符的情况下初始化变量。有人可以向我解释一下吗?很难对特定问题进行谷歌搜索。
答案 0 :(得分:4)
出于历史和效率的原因,C ++(和C)memory management是明确的和手动的。
有时,您可以在call stack上进行分配(例如,使用VLA或alloca(3))。但是,这并不总是可行的,因为
您绝对应该阅读garbage collection和dynamic memory allocation。在某些语言(Java,Ocaml,Haskell,Lisp,....)或系统中,提供了GC,并负责释放无用(更确切地说是无法访问的)数据的内存。另请阅读weak references。请注意,大多数 GC 需要扫描call stack以获取本地指针。
请注意,拥有相当高效的垃圾收集器(但通常不是C ++)是可能的,但也很困难。对于一些程序,Ocaml-使用世代复制GC-比同等C ++代码更快 - 具有显式内存管理。
明确管理内存有一个优点(在C ++中很重要),你不需要支付你不需要的东西。它给程序员带来了更多的负担带来的不便。
在C或C ++中,您有时可能会考虑使用Boehm's conservative garbage collector。使用C ++,您可能有时需要使用您自己的分配器,而不是默认的std::allocator。另请阅读smart pointers,reference counting,std::shared_ptr,std::unique_ptr,std::weak_ptr和RAII成语以及rule of three(在C ++中,成为5)的规则。最近的智慧是避免使用明确的new
和delete
(例如,使用标准containers和智能指针)。
请注意,管理记忆中最困难的情况是任意的,也许是圆形的(参考)图形。
在Linux和其他一些系统上,valgrind是一种有用的工具来搜寻memory leaks。
答案 1 :(得分:3)
替代方案,在堆栈上进行分配会给您带来麻烦,因为堆栈大小通常限制在Mb级别,并且您将获得大量有价值的副本。您还会在函数调用之间共享堆栈分配的数据时遇到问题。
还有其他选择:使用std::shared_ptr
(C ++ 11以后)将在不再使用共享指针时为您执行delete
。共享指针实现利用由可怕的首字母缩略词RAII引用的技术。我明确地提到它,因为大多数资源清理习语都是基于RAII的。您还可以使用C ++标准模板库中提供的全面数据结构,这样就无需通过显式内存管理来解决问题。
但正式而言,每个 new
必须与delete
平衡。同样适用于new[]
和delete[]
。
答案 2 :(得分:2)
实际上,在许多情况下,不需要new
和delete
,您只需使用标准容器,并为其分配/解除分配管理。
您可能需要明确使用分配的原因之一是身份很重要的对象(即它们不仅仅是可以复制的值)。
例如,如果你有一个gui"窗口"对象然后制作副本可能没有意义,因此您或多或少地排除了所有标准容器(它们是为可以复制和分配的对象而设计的)。在这种情况下,如果对象需要在创建它的函数中存活,那么最简单的解决方案就是在堆上显式分配它,可能使用智能指针来避免泄漏或使用后删除。
在其他情况下,避免复制可能很重要,不是因为它们非法,而是效率不高(大对象)并明确处理实例生命周期可能是更好(更快)的解决方案。
显式分配/解除分配可能是最佳选择的另一种情况是标准库无法表示的复杂数据结构(例如,每个节点也是双向链表的一部分的树)。
答案 3 :(得分:2)
现代C ++样式经常对专门资源管理代码之外的new
和delete
的显式调用不满。
这不是因为堆栈/自动存储就足够了,而是因为RAII智能资源所有者(无论是容器,共享指针还是其他东西)几乎所有的直接内存争论都是无关紧要的。由于内存管理问题通常容易出错,这使您的代码更健壮,更易于阅读,有时更快(因为花哨的资源所有者可以使用您可能无处可去的技术)。
这是零规则的例子:不写析构函数,复制/移动赋值,复制/移动构造函数。将状态存储在智能存储中,并让它为您处理。
当您自己编写拥有类的智能内存时,以上都不适用。然而,这是一件罕见的事情。它还需要C ++ 14(make_unique
)来摆脱调用new
的倒数第二个借口。
现在,在上述风格下,仍然使用免费商店,而不是直接使用。需要免费存储(也称为堆),因为自动存储(也称为堆栈)仅支持非常简单的对象生存期规则(基于范围,编译时确定性大小和计数,FILO顺序)。由于运行时大小和计数数据很常见,并且对象生存期通常不那么简单,因此大多数程序都使用免费存储。有时在堆栈上复制一个对象就足以使简单的生命周期成为一个问题,但在其他时候,身份是很重要的。
最后一个原因是堆栈溢出。在某些C ++实现中,堆栈/自动存储的大小受到严重限制。更重要的是,当您在其中放入大量内容时,很少有可靠的故障模式。通过在免费商店中存储大数据,我们可以减少堆栈溢出的可能性。
答案 4 :(得分:1)
只有在简单的程序中,您才能事先知道自己使用了多少内存。一般来说,你无法预见你会使用多少内存。
然而,对于现代C ++ 11,您通常依赖标准库(如vector
和map
进行内存分配,而智能指针的使用可帮助您避免内存泄漏,因此您并不真正需要手动明确使用new
和delete
。
答案 5 :(得分:1)
当您使用新时,您的对象会存储在堆中,并且它会一直存在,直到您不手动删除它为止。但在不使用新的情况下,您的对象进入堆栈,当它超出范围时会自动销毁。 堆栈设置为修复大小,因此如果没有任何用于分配新对象的块,则会发生堆栈溢出。当调用许多嵌套函数或者存在无限递归调用时,通常会发生这种情况。如果堆的当前大小太小而无法容纳新内存,则操作系统可以将更多内存添加到堆中。
答案 6 :(得分:1)
另一个原因可能是您使用C风格的接口显式调用外部库或API。在这种情况下设置回调通常意味着必须在回调中提供和返回上下文数据,并且这样的接口通常仅提供“简单”的void *或int *。使用new分配对象或结构适用于此类操作(如果需要,可以在回调中稍后删除它)。
答案 7 :(得分:1)
首先,如果您不需要动态分配,请不要使用它。
需要动态分配的最常见原因是
该对象的生命周期由
程序逻辑而不是词法范围。 new
和。{
delete
运算符旨在支持显式托管
寿命。
另一个常见原因是尺寸或结构
"对象"在运行时确定。对于简单的情况(数组,
等)有标准类(std::vector
)
处理这个,但对于更复杂的结构(例如
图表和树木),你自己必须这样做。 (通常
这里的技术是创建一个表示图形或类的类
树,让它管理记忆。)
并且存在对象必须是多态的情况,并且
直到运行时才能知道实际类型。 (有一些
在没有动态分配的情况下处理这个问题的棘手方法
最简单的情况,但一般来说,你需要动态分配。)
在这种情况下,std::unique_ptr
可能适合处理
delete
,或者必须共享对象,std::shared_ptr
(虽然通常,必须共享的对象属于
第一类,上面,所以智能指针不是
适当)。
也可能有其他原因,但这些是 我经常遇到的三个。