C++中的析构函数(与Java相比)

3
到目前为止,我一直在用Java编写程序。所以当我开始学习C++时,我首先想到的是如何销毁/删除/完成我不再需要的对象。
在Java中,我习惯将它们设置为null,这样垃圾收集器就会处理它。然而,我不知道C++的情况。我找到了这篇文章http://en.wikipedia.org/wiki/Comparison_of_Java_and_C%2B%2B,解决了我大部分的问题。但还有一些事情我没明白。
1)在Java中,有一种方法可以强制垃圾收集器立即清理(这并不总是有用,因为它等待一些垃圾堆积才运行)。有没有办法在C++中做到这一点?
2)(C ++)还有与上述相反的情况,我如何使对象处于“标记为删除”的状态,程序决定何时清理它(像Java一样)?

3) (C++)我是否应该强制垃圾收集器立即清理(我很确定这不是正确的方法,但为了确保我正在询问)?

如果您能提供一个小代码示例,以演示哪些代码会触发什么,我将不胜感激。


9
C++不提供垃圾回收(虽然可以实现,但是非常麻烦,通常也不需要)。相反,它使用RAII(资源获取即初始化),这使得析构函数非常有用。 - user1203803
5
找一本书。在这个领域,C++和Java之间的差异就像汽车和火车一样大。 - Mooing Duck
2
你读了你链接的文章吗?C++没有垃圾回收器。你的问题没有意义。 - JB Nizet
1
我也建议阅读一本好的入门书籍 - Georg Fritzsche
@A.H.:我实际上已经将类比更改为不会暗示一个是普遍更好的东西,它们只是不同而已。我并没有做出任何关联。(如果我必须选择的话,我会说C++就像汽车。可以去更多的地方,但你必须自己控制它,很容易撞车毁掉你的生活。) - Mooing Duck
显示剩余2条评论
5个回答

6

1)如果您的对象在自动存储中,您可以限制它们的作用域:

{
   X x;
   //...
}  //x gets destructed here

如果是在动态存储中,使用完后请删除它们:

X* x = new X;
//...
delete x; //x gets destructed

2) 你不能(至少没有简单的方法)。即使是把指令写在结尾大括号里,你也必须告诉C++何时删除对象。(参见第一个代码片段)

3) C++ 中没有垃圾收集器。请看下面两个代码片段。如果对象在动态存储中,你必须显式地删除它们;如果在自动存储中,它们将被自动删除(但不是由垃圾回收器)。

值得一提的是智能指针(有很多实现),但那也不是垃圾回收器。它只是帮助你节省管理内存的烦恼。但它与Java完全不同。


提到智能指针要点赞。在Stack Overflow上有一篇非常好的智能指针介绍文章。请注意,C++11标准库已经支持智能指针,因此不再需要使用Boost。 - user1203803

4

C++在这方面与Java非常不同,因此以下是简要概述:

分配:为对象分配内存。
构造:准备使用对象。
析构:对象完成所有操作并解散自身。
释放:将内存归还给系统。

int main() {
    int myint;  //automatic int object is allocated and constructed
    //stuff
}   // when main ends, automatic int object is destroyed and deallocated

int main() {
    int* mypointer;  //automatic pointer object is allocated and constructed
    mypointer = new int;  //dynamic int object is allocated and constructed
    //stuff
    delete mypointer; //dynamic int object is destroyed and deallocated 
}   // when main ends, automatic pointer object is destroyed and deallocated
    // note: Pointers to _not_ delete the object they point to.

class myclass {
    //members
public:
    myclass() {} //this is the default constructor
    myclass(const myclass& rhs) {} //this is the copy constructor
    myclass& operator=(const myclass& rhs) {return *this} //this is the assignment operator
    ~myclass() {} //this is the destructor
};

当函数结束时,函数本身中的所有变量(我们称之为自动变量)都会调用其析构函数,然后自动释放。这意味着对于函数内部的本地对象,它们在函数结束的那一刻自动清理自己。该规则也神奇地适用于类的成员。当销毁一个类的实例时,它的每个成员将自动被销毁。这意味着大多数析构函数为空。

如果手动分配了内存(使用new关键字),则必须使用delete关键字手动销毁和释放内存。当您调用delete时,它将立即销毁(并deallocating)并且不会继续执行直到完成。如果您忘记了,它将永远不会被释放(尽管一些操作系统会在程序结束时处理它)。

由于人们常常犯错误,因此当需要使用动态对象时,“正确”的做法是:

int main() {
    std::unique_ptr<myclass> myptr = new myclass(); //allocate and construct
} //both the unique_ptr and the dynamic object are destroyed and deallocated

unique_ptr 足够聪明,能够自动清理指向的对象,让你从更大的问题中解放出来。

C++ 这样做的原因是,如果你有一个表示文件的对象 F,它可能对该文件拥有独占锁。在 C++ 中,一旦销毁了 F,就可以立即创建使用同一文件的对象 G。在 Java 中,无法保证 finalizer 会执行,这意味着文件可能会一直保持锁定,直到程序结束。(虽然不太可能)


3

C++中没有垃圾回收器,你需要自己编写和运行析构函数。在C++中,经常会忘记运行析构函数,这是常见的错误。

如果你的对象是使用new分配的内存,则应该使用delete释放它。因此,new调用构造函数,而delete调用析构函数。

myclass *p = new myclass();
// do something
delete p;

这称为动态对象分配。

如果您的对象被 "正常" 定义,它将在超出作用域时自动销毁。

myclass a;
// do something
// will destructed when }

这被称为自动对象分配。

另外在Java中,由于垃圾收集器的发明,您也不需要分配null值,因为垃圾收集器会自行处理对象的删除。


1
你能将“defined normally”改成更正式的说法吗?只有你知道那意味着什么。 - Luchian Grigore
1
尝试过。在C++中,您可以创建“堆上”的对象,就像在Java中一样。在这种情况下,您将收到一个指向对象的指针,您应该在最后传递给delete。另一种方式是“正常”的方式,即以“按值”方式创建对象,这仅适用于Java中的整数类型。 - Suzan Cioc
3
堆和栈是实现细节,不属于C++术语范畴,在这种情况下应称为自动存储和动态存储。在C++中没有“正常”的对象分配方式。 - Luchian Grigore
最近我提出了一个问题,你可能想看一下 - https://dev59.com/Qmox5IYBdhLWcg3wekMs - Luchian Grigore

1

C++使用RAII(Resource Acquisition Is Initialization)编程习惯,没有像Java的垃圾回收器(Garbage Collector)或Objective-C 2中的AutoZone一样的自动内存管理。因此,正确的实例清理可能会变得复杂。回答你的问题:

附加说明1:C++中没有垃圾回收器,所以你必须手动删除对象,或使用引用计数技术或更好的智能指针。智能指针现在是C++11标准的一部分,但据我所知,目前还没有任何一个C++编译器提供支持。目前你可以使用Boost库中的智能指针模板:http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/smart_ptr.htm。新的C++标准直接采用了Boost的实现,在不久的将来切换到新标准时不会有任何问题(MSVC 2012将实现对C++11的支持)。

附加说明2:无法进行标记操作,只需在适当的位置“手动”删除它,或将此任务交给智能指针。

附加说明3:不适用。

最后,总有一种最简单的选择 - 不要在堆上分配对象,也就是动态分配。在Java中没有这样的可能性,但在C++中有。我甚至在Stroustrup(C++的创造者)关于C++编程的一些伟大著作中读到过,在C++创建时期不推荐使用这种动态分配。他说:为了使RAII正常工作,不能进行动态分配 - 这在今天听起来很奇怪,但这是Stroustrup写的,不是我自己想的,我个人几乎所有东西都是动态分配的,就像每个人一样...

静态分配的主要原因是对象一旦超出范围就会被删除,因此无需担心异常安全和清理。如果您动态分配实例,则如果实例离开当前范围,则不会自动删除它 - 您将面临内存泄漏 - 如果您不手动删除实例。考虑简单的try-catch块:

try 
{
Class *instance = new Class;
//some error
}
catch(...)
{
//error caught - current execution is terminated immediately, instance is no deleted - memory leak.
}

在Java中,有一个finally语句块总是会被调用,这样当异常抛出时就可以执行必要的清理操作。但是在C++中,你会遇到麻烦...除非你使用了提到的智能指针或一些非常类似的技术。使用智能指针后,你就不必再担心清理工作了(实际上并非完全如此,但你的生活肯定会更轻松,你的代码也会更少出错)。

主要的C++编译器(MSVC、Intel、GCC和Clang)都对C++11有一定的支持,但支持程度因编译器而异。由于新的智能指针主要是库扩展,因此它们得到了广泛的支持。VS 2010、gcc至少从4.3开始以及带有libc++的clang都具备这些功能。 - bames53
此外,我通常很少使用动态分配,更喜欢使用自动存储期变量。我认为你会发现,在C++中直接使用动态分配比你的评论“几乎所有人都动态分配”所示要少得多(如果不是罕见的话,那么在我看来,人们编写C++的方式是错误的)。大多数情况下,我可能会直接使用可以为我处理它的类型,而不是直接自己处理它,例如使用vector进行动态数组。 - bames53
感谢您对我的回答进行澄清和补充。实际上,我知道智能指针的支持,但不想过于复杂化我的回答。是的,我也经常使用静态分配,可能比动态分配更多。我已经了解到动态分配似乎更为"客观",因此被过度使用 :) 但我也不认为这是一种不好的做法。 - vitakot

1

C++中的垃圾回收总是立即执行的。没有单独的垃圾收集器;当您删除一个对象时,它会立即在当前线程上被删除。代码如下:

MyObject* foo = new MyObject();
...
delete foo;

有垃圾收集框架可用于C ++,您还可以研究智能指针,它们也是垃圾收集的一种形式。

请注意James下面的评论--对象的析构函数和operator delete总是立即调用,但在实现中是否立即可用取决于具体情况。


3
只是一点小注,但在C++中释放内存时并不能保证它会立即可用。我知道有些系统在释放在不同线程中分配的内存时会延迟回收,还可能存在其他情况。 - James Kanze
除非您调用 delete,否则该对象将永久分配,即使它变得不可访问。 - Ted Hopp
@JamesKanze -- 如果我说错了,请纠正我,但析构函数本身总是立即调用的,对于内存何时变得可用于未来的新语句,我想那更多取决于实现。 - Nathan Monteleone
@NathanMonteleone 析构函数会立即被调用。operator delete()函数也会立即被调用。无论operator delete()函数是否立即使内存可用,这是另一个问题——我知道的至少有一种实现方式,例如,每个线程使用单独的内存池;如果要删除的内存是由不同的线程分配的,则将其放入列表中以供稍后由该线程处理。 - James Kanze

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