14

如果我显式调用析构函数( 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

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

谢谢!

4

11 回答 11

16

答案是……几乎总是。

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

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

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

于 2009-06-24T01:52:19.337 回答
10

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

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

此外,也许更重要的是,您将new“正常”进行放置,而不是这种混合常规new/ 放置new解决方案。我并不是说它不起作用,我只是说它是一个相当,啊,对你的记忆问题的创造性解决方案。

于 2009-06-24T01:18:42.570 回答
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,以便在调用析构函数的第二次不会发生任何事情(如删除 null指针什么都不做)。

于 2009-06-24T03:14:11.560 回答
6

是的,一个析构函数,即使显式调用,也会正确地销毁它的子对象。

正如您似乎意识到的那样,这是一个罕见的操作,但也许作为经过良好测试和记录的库的一部分,它可能很有用。但是记录(和描述)它,因为即使它是有效和安全的,每个维护者(包括你)都不会对它感到满意。

于 2009-06-24T01:16:11.517 回答
5

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

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

于 2009-06-24T02:36:36.953 回答
3

是的。析构函数按 LIFO 顺序调用任何成员析构函数,然后是基类析构函数,没有办法阻止它调用这些析构函数*。对象堆栈保证展开。

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

编辑:

  • 我想通过调用 abort() 或 longjmp() 实际上可以防止成员和基类析构函数运行。
于 2009-06-24T01:50:05.737 回答
2

STL 容器执行此操作。事实上,STL 分配器必须提供调用对象析构函数的销毁方法(分配器还提供解除分配用于保存对象的内存的方法)。然而,Stroustrup ( The C++ Programming Language 10.4.11) 的建议是

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

于 2009-06-24T08:17:43.527 回答
2

运行析构函数不会释放被析构对象使用的内存 - 删除运算符会这样做。但是请注意,析构函数可能会删除“子对象”,并且它们的内存将照常释放。

您需要阅读有关放置 new/delete 的内容,因为这允许您控制内存分配以及构造函数/析构函数何时运行。

请参阅此处了解一些信息:

http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.9
于 2009-06-24T01:18:51.540 回答
1

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

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

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

于 2009-06-25T13:33:30.043 回答
0

我会考虑为您想要特殊分配和释放行为的对象覆盖 new - 毕竟这就是它的用途。普通 new 和显式调用析构函数的混合方案听起来像是未来头痛的秘诀。对于初学者来说,任何内存泄漏检测策略都会被抛弃。

于 2009-06-24T07:50:52.910 回答
0

为什么要彻底摧毁它?只写内存?您是否希望执行逻辑以优雅地处理释放资源?我要强调的是,这是对语言的滥用,不是一个好主意。

于 2009-06-24T02:18:17.303 回答