标准C++中的垃圾回收是否自动进行?

14

据我所知,在标准C++中,每当您使用new运算符时,必须在某个时刻使用delete运算符以防止内存泄漏。这是因为C++中没有垃圾回收机制。在.NET中,垃圾回收是自动进行的,因此不需要担心内存管理。我的理解正确吗?谢谢。


9
百分之百正确! - henle
11
99.9%正确。有些情况下你需要关注.NET中的内存管理。 - jmucchiello
1
在C++/CLI中,您可以使用'gcnew'进行分配,但是您仍然可以使用'new',但是它们需要与'delete'配对,就像在完全未管理的代码中一样。 - AndersK
几乎正确。有时候你必须显式地处理资源,而不是等待垃圾回收自动处理。 - Sergey
标题有误导性,您可能想要更改它(您是指 .NET 而不是 C++ 吗?) - JRL
8个回答

20
长答案是,每次调用new时,都必须在某个地方以某种方式调用delete或其他释放内存的函数(取决于内存分配器等)。
但你不需要自己提供delete调用:
  1. C++有垃圾回收机制,比如Hans-Boehm Garbage Collector。还可能有其他垃圾回收库。
  2. 可以使用智能指针,它们使用RAII(如果指针允许共享访问,则使用引用计数)来确定何时删除对象。一个很好的智能指针库是Boost的smart pointer。在绝大多数情况下,智能指针可以替换裸指针。
  3. 一些应用程序框架,比如Qt,构建对象树,使得框架的堆分配对象之间存在父子关系。因此,只需对一个对象调用delete,所有子对象也将自动被delete
如果你不想使用这些技术来防止内存泄漏,可以尝试使用内存检查工具。Valgrind特别好用,但它只能在Linux上运行。
关于.NET,使用gcnew进行分配意味着内存由.NET跟踪,因此不会发生泄漏。然而,其他资源,如文件句柄等,不受GC管理。

6
RAII的一个优点(!)是可以管理除内存以外的资源,例如文件句柄。删除对象后,拥有该文件的对象也将被关闭。 - casualcoder
在我们谈论缩写时,不要忘记SBRM。例如,fstream将在其超出范围时自动关闭文件。还要记住,成员变量的作用域是父对象的生命周期,即使没有析构函数,这也使得SBRM非常方便。 - joshperry
1
我认为你提到的 fstream 的 SBRM 是指 RAII。 - blwy10
1
@joshperry。始终存在析构函数(虽然可能是由编译器生成的)。 - Martin York

10
惯用语高级C++中,你永远不会调用delete
C++没有像C#中那样的标准垃圾收集器,因此基本上,newdelete需要成对出现。然而,在C++中有机制完全消除了现代风格代码中显式使用delete的必要性。
首先需要注意的是,在C++中,你使用new的频率比在C#中要少得多。这是因为在C#中,你在创建结构、类或数组实例时都使用new,但在C++中,只有当你想动态管理数据元素时才使用new。大多数数据在C++中不需要动态管理,因此可以在不使用new的情况下创建。[换句话说,在C++中,new的含义与在C#中不同。在C++中,它特指动态分配,而在C#中则用于任何构造。]
其次,在C++中,任何时候调用new时,返回值应直接交给智能指针。智能指针将确保在适当的时间自动调用delete
顺便提一句,除非您是编写低级库的大师(或学习如何这样做的学生),否则不应在C ++中调用 new 来分配数组。标准库(以及Boost / TR1)提供模板类,为您分配和管理数组。
总之,C ++不使用垃圾收集器,但它具有自己的形式的< strong>自动内存管理。两种方法之间存在微妙的差异,但两种方法都自动释放内存,从而消除了大多数类型的内存泄漏。
C ++创建者Bjarne Stroustrup在回答问题时给出了这些概念的权威演示:我如何处理内存泄漏? 另请参见:

同样地,如果您确实使用了delete,那么这很可能是一个错误。问题在于使用delete假定您精确知道程序的控制流程,以便删除操作会在适当的时间发生。这是一个简单但幼稚的假设。首先,它需要一定的小心谨慎才能正确地定位删除操作。更重要的是,异常通常可以在代码的任何中间点发生,并违反您对控制流的假设。使用try/catch修复这个问题是可能的,但这不是最佳实践。 - Brent Bradburn
1
Bjarne的演示有些过时,特别是他使用auto_ptr<>,这在现今通常被避免(有更好的替代方案)。但这展示了我之前所说的“现代风格”的成熟。Bjarne已经教授这种方法很长时间了。 - Brent Bradburn
不错的视频:编写现代C++代码:C++如何在这些年中发展 - Brent Bradburn
可能“微妙”并不是描述C#和C++内存管理差异的正确方式。一般来说,相似之处仅限于——你不需要调用“delete”。 - Brent Bradburn
如果C++弃用new并将其替换为dynamic,那会很有趣。我认为这会更加直观...也许排除稀有的“放置new”用法。 - Brent Bradburn
显示剩余2条评论

8
你关于operator new的说法是完全正确的...但它过于简化了C++语义。
在C++中,对象可以在堆栈或堆上创建:
class Foo {};

int main() {
  Foo obj1;
  Foo* obj2 = new Foo();
  delete obj2;
}

在上面的例子中,obj1被创建在栈上,而obj2是在堆上创建的(使用new关键字)。在堆上创建的对象不会被销毁,直到显式调用delete。然而,在栈上的对象在超出作用域时自动销毁(例如,在这个例子中,当main()返回时)。
这使得C++中的“资源获取即初始化”惯用语(也称为RAII)比基本垃圾收集更加强大。需要清理的资源(堆内存、套接字、文件、数据库连接等)通常放在基于栈的对象中,其析构函数负责清理。
相比之下,Java和C#不允许对象在栈上构造,并且不能保证收集是否发生或最终器是否运行(我不是C#专家,所以可能有些错误)。因此,尽管在Java/C#中可以获得免费的堆内存管理,但实际上你会在这些语言中比在C++中需要更多的资源清理代码。

2

是的,你说得对,在标准C++中(在托管C++或其他变体中则不同),每次使用new后必须使用delete。但在C#、Java和其他垃圾回收语言中,则无需这样做(实际上大多数语言都没有相应的"delete"运算符)。


1

在C++中没有垃圾回收。

正确。


2
不是这样的。有垃圾收集器可用(而且自己构建也不难)。 - Martin York
1
“可用”还是“语言的标准功能”?这个问题是关于语言的标准功能。不是可用性。智能指针(在我看来)是必不可少的,但不是语言的标准功能。 - S.Lott

1
你可以以两种方式在.NET中使用C++:托管或非托管。在托管模式下,.NET的垃圾回收机制会代表你释放内存;在非托管模式下,你接近于C++的正常/标准行为,因此你需要自己管理内存。

4
无论是哪种“模式”,每个 new 都是未受管控的内存,必须通过 delete 手动释放。C++/CLI 添加了 gcnew,但这不是 C++ 本身,而是一种语言扩展。 - Pavel Minaev
3
实际上我们在谈论两种不同的编程语言。C++ 和 C++/CLI。 - Martin York

1

自动垃圾回收非常有用,但仍然可能出现内存泄漏问题,就像这个问题所展示的:

C# WPF中的内存泄漏

虽然在.NET和Java中已经减少了这种情况,但这并不意味着它可以自动处理不良编码。

因此,在C++中,您需要显式释放您请求的内容,我认为这有时更好,因为您知道发生了什么。我希望在.NET和Java中,垃圾回收器在调试模式下做得更少,以帮助确保人们知道他们正在做什么。


0

正确,你必须担心C++的垃圾回收

而且...在.NET上不需要担心垃圾回收

只有当你有如此密集和长时间的脚本需要优化时,你才需要关注它。

编辑:asveikau和Pavel Minaev的评论都很好,谢谢!我过于概括了信息。


我不会完全说你“不用担心它”。对于某些类型的对象,在C#中,你确实需要小心地执行手动清理。例如,你应该确保关闭文件,而不是依赖GC来为你完成。 - asveikau
1
当然,您可以在C#(或其他.NET语言)中分配未托管的内存,但这意味着您必须自己释放它。请参见Marshal.AllocHGlobal等函数。 - Pavel Minaev

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