调用析构函数是否显式地完全破坏了一个对象?

时间:2009-06-24 01:12:00

标签: c++ destructor

如果我显式地调用析构函数(myObject .~Object())这是否确保对象将被适当地销毁(调用所有子析构函数)?

好一些代码:

class Object
{
   virtual ~Object()
   {}
};

class Widget : public Object
{
   virtual ~Widget()
   {}
};

...
Object* aWidget = new Widget(); //allocate and construct
aWidget->~Object(); //destroy and DON'T deallocate

我知道我可以删除该对象,但我不想这样做。我希望将分配的内存作为一项重要的优化保留。

谢谢!

11 个答案:

答案 0 :(得分:16)

答案是......几乎总是如此。

如果您的对象具有非虚拟析构函数,然后进行子类化以添加需要释放的子元素...则在对象基类上调用析构函数将不会释放子元素。这就是为什么你应该总是声明析构函数是虚拟的。

我们有一个有趣的案例,其中两个共享库引用了一个对象。我们更改了定义以添加需要释放的子对象。我们重新编译了第一个包含对象定义的共享库。

但是,第二个共享库没有重新编译。这意味着它不知道新添加的虚拟对象定义。从第二个共享库中调用的Delete简称为free,并且没有调用虚拟析构函数链。结果是一个讨厌的内存泄漏。

答案 1 :(得分:10)

是。但圣洁的烟雾,你确定吗?如果是这样,我会使用placement new构建您的Widget。使用放置new然后显式调用析构函数是一种可接受的,如果不寻常的习惯用法。

编辑:考虑手动分配内存,而不是使用new分配第一个对象,然后重新使用其内存。这使您可以完全控制内存;例如,你可以一次分配大块,而不是为每个Widget分配一个单独的内存块。如果记忆确实是如此稀缺的资源,这将是公平的节省。

此外,也许更重要的是,您可以正常进行展示位置new“,而不是这种混合常规new /展示位置new解决方案。我不是说它不起作用,我只是说这是一个相当的,啊,创意解决你的记忆问题。

答案 2 :(得分:6)

是的,析构函数,即使被明确调用,也会正确地破坏它的子对象。

正如您似乎意识到的那样,这是一项罕见的行动,但也许作为经过良好测试和记录的库的一部分,它可能会有用。但是文档(和简介)虽然它是有效和安全的,但每个维护者(包括你)都不会对它感到满意。

答案 3 :(得分:6)

是的,它将调用所有子析构函数,以便它可以按预期工作。

析构函数毕竟只是一个函数,当对象被删除时它就会被调用。

因此,如果你使用这种方法,请注意:

#include <iostream>

class A
{
public: 
    A(){};
    ~A()
    {
        std::cout << "OMG" << std::endl;
    }
};

int main()
{
    A* a = new A;
    a->~A();
    delete a;
    return 0;
}

output:
OMG
OMG 

在对象上实际调用delete时第二次调用析构函数,因此如果在析构函数中删除指针,请确保将它们设置为0,以便第二次调用析构函数时不会发生任何事情(如删除空指针什么都不做。)

答案 4 :(得分:5)

请保存一些真正的麻烦并使用Boost Object Pool,这听起来像是您的源/接收器模式的现有实现。它将分配大块内存,将它们切成适合您对象的大小并将它们返回给您(在调用构造函数之后)。当您删除对象时,它们会调用它们的析构函数,并将它们放入对象的链接列表中以供重用。它会自动增长和缩小,并确保对象的实例在内存中相互靠近。

如果不出意外,它是一个很好的示例实现,可以放置新的和显式使用的构造函数。

答案 5 :(得分:3)

是。析构函数以LIFO顺序调用任何成员析构函数,然后调用基类析构函数,并且无法阻止调用这些析构函数*。保证对象堆栈放松。

初始化和终结与C ++中的内存分配和释放完全分开,以便在出现特殊情况时,应用程序员可以通过明确的语法向编译器表达他或她的意图。

编辑:

  • 我认为通过调用abort()或longjmp()实际上可以防止成员和基类析构函数运行。

答案 6 :(得分:2)

运行析构函数不会释放被破坏对象使用的内存 - 删除操作符会这样做。但请注意,析构函数可能会删除“子对象”,并且会按照惯例释放它们的内存。

您需要阅读放置新/删除,因为这允许您控制内存分配以及构造函数/析构函数运行时。

请点击此处获取一些信息:

http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.9

答案 7 :(得分:2)

STL容器这样做。实际上,STL分配器必须提供一个调用对象析构函数的destroy方法(allcators还提供一个deallocate方法来释放用于保存对象的内存)。但是,Stroustrup( C ++编程语言 10.4.11)的建议是

  

请注意,应尽可能避免显式调用析构函数。有时,它们是必不可少的。 ......在明确调用析构函数之前,新手应该三思而后行,并在此之前询问更有经验的同事。

答案 8 :(得分:1)

调用析构函数很好。但是,请注意您正在调用它的类型。如果该类型没有(没有继承)虚拟析构函数,则可能会出现意外行为。

另外,如上所述,析构函数不释放任何内存,但我想这就是你想首先手动调用它的原因。

另外,除非我弄错了,否则如果你使用placement new来调用构造函数,那么手动调用析构函数是唯一的选择。

答案 9 :(得分:0)

为什么要毁掉它?只是写在记忆中?您是否希望执行逻辑以优雅地处理释放资源?我将强调说这是对语言的滥用而不是一个好主意。

答案 10 :(得分:0)

我会考虑为你想要特殊分配和释放行为的对象覆盖new - 毕竟这就是它的用途。正常的新的和明确调用的析构函数的混合方案听起来像是未来头痛的秘诀。首先,任何内存泄漏检测策略都会被抛弃。