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

Posted

技术标签:

【中文标题】C++ 中的析构函数(与 java 相比)【英文标题】:Destructors in C++ (Compared to java) 【发布时间】:2012-03-05 03:31:04 【问题描述】:

到目前为止,我一直在用 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++)我应该强制垃圾收集器就地清理吗(我很确定这不是正确的方法,但我只是想确定一下)?

如果你能给出一个小代码示例,我会很感激它,其中代码触发了什么。

【问题讨论】:

C++ 不提供垃圾收集(您可以实现它,但它确实很麻烦,通常不需要)。它有RAII,这使得析构函数非常有用。 找一本书。 C++ 和 Java 在这方面的区别就像汽车和火车一样。 你读过你链接的文章吗? C++ 没有垃圾收集器。你的问题没有意义。 我第二次阅读一个好的introductory book。 Java 没有办法强制垃圾回收。当您调用System.gc() 时,它会告诉运行时这将是开始垃圾收集的好时机,但 GC 实际上会在未来不确定的时间在单独的线程上运行。 【参考方案1】:

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


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

如果在动态存储中,完成后将其删除:

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

2) 你不能(至少以干净的方式)。您必须指示 C++ 何时删除您的对象,即使该指令包含一个结束括号。 (见第一个代码sn-p)

3) C++ 中没有垃圾收集器。请参阅两个 sn-ps。您要么必须显式删除对象(如果在动态存储中),要么如果在自动存储中,它们将被自动删除(但不是由垃圾收集器)。

值得研究的是智能指针(那里有很多实现),但这也不是垃圾收集器。它只是为您节省了管理内存的麻烦。但它与 Java 完全不同。

【讨论】:

+1 用于提及智能指针。对智能指针on Stack Overflow 有很好的介绍。请注意,C++11 标准库具有智能指针,因此不再需要为此使用 Boost。【参考方案2】:

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 时,它会立即销毁(并取消分配),并且在完成之前不会继续。如果你忘记了,它永远不会被释放(尽管,一些操作系统会在你的程序结束时释放它)。

由于人们会犯错误,所以当您需要动态对象时,“正确”的做法是:

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 中分配空值,因为垃圾收集器是为了不打扰对象删除而发明的。

【讨论】:

你能把“正常定义”改成更正式的吗?只有你知道这意味着什么。 试过了。在 C++ 中,您可以在“堆上”创建对象,就像在 Java 中一样。在这种情况下,您会收到一个指向对象的指针,您应该在最后将其传递给delete。另一种方式是“正常”,即以“按值”方式创建对象,这在 Java 中仅适用于整数类型。 堆和堆栈是实现细节,在此上下文中,不是 C++ 命名法的一部分。这些术语是自动和动态存储。在 C++ 中没有分配对象的“正常”方式。 我最近问了一个问题来解决这个问题,可能想看看它 - ***.com/questions/9181782/…【参考方案4】:

C++ 使用 RAII (Resource Acquisition Is Initialization) 编程习惯,没有像 java 中的垃圾收集器或 Objective-C 2 中的 AutoZone 这样的自动内存管理。因此,正确的实例清理很容易变得复杂。回答您的问题:

ad 1:C++ 中没有 GC,因此您必须手动删除对象或使用引用计数技术或更好的智能指针,它们现在是 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 都有它们。 另外,我通常很少使用动态分配,更喜欢使用自动存储持续时间变量。我想你会发现在 C++ 中直接使用动态分配比你的评论“像每个人一样动态分配几乎所有东西”所表明的要少得多。 (如果这种情况并不罕见,那么 IMO 的人写错了 C++)。大多数情况下,可以直接使用动态分配,我更喜欢使用可以为我处理它的类型,而不是直接自己做,比如 vector 用于动态数组。 感谢您的澄清和完成我的回答。实际上,我知道智能指针支持,但不想使我的答案过于复杂。是的,我也经常使用静态分配,可能比动态分配更多。我读过动态分配看起来更“客观”,因此被过度使用:)但我也不认为这是一个不好的做法。【参考方案5】:

C++ 中的垃圾收集始终是即时的。没有单独的垃圾收集器;当您删除一个对象时,它会立即在当前线程上被删除。它看起来像这样:

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

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

请注意下面 James 的 cmets —— 对象的析构函数和操作符 delete 总是被立即调用,但它取决于内存是否立即可用。

【讨论】:

只是一点点,但不能保证当您在 C++ 中释放内存时,它会立即可用。我知道系统会延迟释放在不同线程中分配的内存,并且可能还有其他情况。 除非您调用delete,否则该对象将保持永久分配状态,即使它变得无法访问。 @JamesKanze -- 如果我错了,请纠正我,但析构函数本身总是被立即调用,对吗?至于内存何时可用于未来的新语句,我想这更依赖于实现.. @NathanMonteleone 立即调用析构函数。 operator delete() 函数也会被立即调用。 operator delete() 函数是否使内存立即可用是另一个问题——我知道至少有一个实现,例如,每个线程使用单独的内存池;如果被删除的内存是由不同的线程分配的,它只是将它放在一个列表中以供该线程稍后处理。

以上是关于C++ 中的析构函数(与 java 相比)的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ 中的析构函数中恢复对象?

C++中的析构函数

C++中基类的析构函数为什么要用virtual虚析构函数

C++中,子类会继承父类的虚函数表!对于父类的析构函数(虚函数) 也会继承吗?

C++之类的析构函数

受保护的与私有的析构函数