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

时间:2021-07-22 20:05:20

If I call a destructor explicitly ( myObject.~Object() ) does this assure me that the object will be appropriately destroyed (calling all child destructors) ?

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

Ok some code:

好吧一些代码:

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

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

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

I know I could just delete the object, but I don't want to. I want to keep the allocated memory handy as an important optimization.

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

Thanks!

11 个解决方案

#1


The answer is... nearly always.

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

If your object has a non-virtual destructor, and is then sub-classed to add child elements that need freeing... then calling the destructor on the object base class will not free the child elements. This is why you should always declare destructors virtual.

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

We had an interesting case where two shared libraries referenced an object. We changed the definition to add child objects which needed freeing. We recompiled the first shared library which contained the object definition.

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

HOWEVER, the second shared library was not recompiled. This means that it did not know of the newly added virtual object definition. Delete's invoked from the second shared library simply called free, and did not invoke the virtual destructor chain. Result was a nasty memory leak.

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

#2


Yes. But holy smokes, are you sure about this? If so I would use placement new to construct your Widget. Using placement new and then explicitly calling the destructor is an acceptable, if unusual, idiom.

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

Edit: Consider allocating the memory yourself manually rather than using new to allocate the first object and then re-using its memory afterward. That allows you complete control over the memory; you could allocate big chunks at a time, for instance, rather than allocating a separate block of memory for each Widget. That'd be fair savings if memory really is such a scarce resource.

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

Also, and perhaps more importantly, you'd then be doing placement new "normally", rather than this hybrid regular new/placement new solution. I'm not saying it won't work, I'm just saying it's a rather, ah, creative solution to your memory problem.

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

#3


Yes, a destructor, even when called explicitly, will destroy its subobjects properly.

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

As you seem to realize, it's a rare action to do, but perhaps as part of a well tested and documented library it may be useful. But document (and profile) it since even though it's valid and safe, every maintainer (including you) will never feel comfortable with it.

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

#4


Yes it will call all the child destructors so it will work as you are expecting.

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

The destructor is just a function after all, it just so happens that it gets called when objects are deleted.

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

Therefore if you use this approach be careful of this:

因此,如果您使用此方法,请注意以下事项:

#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 

The destructor is called a second time when delete is actually called on the object, so if you delete pointers in your destructor, make sure that you set them to 0, so that the second the destructor is called nothing will happen (as deleting a null pointer does nothing).

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

#5


Please save yourself some real headaches and use the Boost Object Pool, which sounds like an existing implementation of your source/sink pattern. It will allocate large chunks of memory, slice them into the correct size for your object and return them to you (after calling the constructor). When you delete objects, they have their destructor called and are put into a linked list of objects for re-use. It will grow and shrink automatically and ensure that instances of your objects tend to be close together in memory.

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

If nothing else, it is a good example implementation of placement new and explicit use of constructors that you could study.

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

#6


Yes. A destructor calls any member destructors in LIFO order, then base class destructors, and there is no way to prevent it from calling these destructors*. The object stack is guaranteed to unwind.

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

Initialization and finalization are separated from memory allocation and deallocation in C++ exactly so that when the special case arises, there is an unambiguous syntax in which the application-programmer can express his or her intent to the compiler.

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

Edit:

  • I suppose that by invoking abort() or longjmp() one could, in fact, prevent the member and base class destructors from running.
  • 我想通过调用abort()或longjmp()实际上可以防止成员和基类析构函数运行。

#7


Running the destructor does not free memory used by the object being destructed - the delete operator does that. Note, however, that the destructor may delete "child objects" and their memory will be freed as per usual.

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

You need to read up on placement new/delete as this allows you to control memory allocation and when constructors/destructors run.

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

See here for a little info:

在这里查看一些信息:

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

#8


STL containers do this. In fact, an STL allocator must provide a destroy method that calls an object's destructor (allcators also provide a deallocate method to deallocate the memory that used to hold an object). However, the advice from Stroustrup (The C++ Programming Language 10.4.11) is

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

Note that explicit calls of destructors ... should be avoided wherever possible. Occassionally, they are essential. ... A novice should think thrice before calling a destructor explicitly and also ask a more experienced colleague before doing so.

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

#9


Calling the destructor is fine. However, beware of the type you're calling it on. If that type doesn't have (didn't inherit) a virtual destructor, you might get unexpected behaviour.

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

Also, as mentioned, the destructor does not free any memory, but I guess that's the reason you want to call it manually in the first place.

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

Plus, unless I'm mistaken, calling the destructor manually is the only option you have if you used placement new to call the constructor.

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

#10


Why destroy it at all? Just write over the memory? Are you wanting logic to execute to gracefully handle releasing resources? I am going to state emphatically that this is an abuse of the language and not a good idea.

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

#11


I would consider overriding new for the objects you want special allocation and deallocation behaviour for - after all that's what it's for. A hybrid scheme of normal new and explicitly calling destructors sounds like a recipe for future headaches. For starters, any memory leak detection strategy is going to get thrown way off.

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

#1


The answer is... nearly always.

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

If your object has a non-virtual destructor, and is then sub-classed to add child elements that need freeing... then calling the destructor on the object base class will not free the child elements. This is why you should always declare destructors virtual.

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

We had an interesting case where two shared libraries referenced an object. We changed the definition to add child objects which needed freeing. We recompiled the first shared library which contained the object definition.

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

HOWEVER, the second shared library was not recompiled. This means that it did not know of the newly added virtual object definition. Delete's invoked from the second shared library simply called free, and did not invoke the virtual destructor chain. Result was a nasty memory leak.

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

#2


Yes. But holy smokes, are you sure about this? If so I would use placement new to construct your Widget. Using placement new and then explicitly calling the destructor is an acceptable, if unusual, idiom.

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

Edit: Consider allocating the memory yourself manually rather than using new to allocate the first object and then re-using its memory afterward. That allows you complete control over the memory; you could allocate big chunks at a time, for instance, rather than allocating a separate block of memory for each Widget. That'd be fair savings if memory really is such a scarce resource.

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

Also, and perhaps more importantly, you'd then be doing placement new "normally", rather than this hybrid regular new/placement new solution. I'm not saying it won't work, I'm just saying it's a rather, ah, creative solution to your memory problem.

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

#3


Yes, a destructor, even when called explicitly, will destroy its subobjects properly.

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

As you seem to realize, it's a rare action to do, but perhaps as part of a well tested and documented library it may be useful. But document (and profile) it since even though it's valid and safe, every maintainer (including you) will never feel comfortable with it.

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

#4


Yes it will call all the child destructors so it will work as you are expecting.

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

The destructor is just a function after all, it just so happens that it gets called when objects are deleted.

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

Therefore if you use this approach be careful of this:

因此,如果您使用此方法,请注意以下事项:

#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 

The destructor is called a second time when delete is actually called on the object, so if you delete pointers in your destructor, make sure that you set them to 0, so that the second the destructor is called nothing will happen (as deleting a null pointer does nothing).

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

#5


Please save yourself some real headaches and use the Boost Object Pool, which sounds like an existing implementation of your source/sink pattern. It will allocate large chunks of memory, slice them into the correct size for your object and return them to you (after calling the constructor). When you delete objects, they have their destructor called and are put into a linked list of objects for re-use. It will grow and shrink automatically and ensure that instances of your objects tend to be close together in memory.

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

If nothing else, it is a good example implementation of placement new and explicit use of constructors that you could study.

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

#6


Yes. A destructor calls any member destructors in LIFO order, then base class destructors, and there is no way to prevent it from calling these destructors*. The object stack is guaranteed to unwind.

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

Initialization and finalization are separated from memory allocation and deallocation in C++ exactly so that when the special case arises, there is an unambiguous syntax in which the application-programmer can express his or her intent to the compiler.

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

Edit:

  • I suppose that by invoking abort() or longjmp() one could, in fact, prevent the member and base class destructors from running.
  • 我想通过调用abort()或longjmp()实际上可以防止成员和基类析构函数运行。

#7


Running the destructor does not free memory used by the object being destructed - the delete operator does that. Note, however, that the destructor may delete "child objects" and their memory will be freed as per usual.

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

You need to read up on placement new/delete as this allows you to control memory allocation and when constructors/destructors run.

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

See here for a little info:

在这里查看一些信息:

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

#8


STL containers do this. In fact, an STL allocator must provide a destroy method that calls an object's destructor (allcators also provide a deallocate method to deallocate the memory that used to hold an object). However, the advice from Stroustrup (The C++ Programming Language 10.4.11) is

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

Note that explicit calls of destructors ... should be avoided wherever possible. Occassionally, they are essential. ... A novice should think thrice before calling a destructor explicitly and also ask a more experienced colleague before doing so.

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

#9


Calling the destructor is fine. However, beware of the type you're calling it on. If that type doesn't have (didn't inherit) a virtual destructor, you might get unexpected behaviour.

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

Also, as mentioned, the destructor does not free any memory, but I guess that's the reason you want to call it manually in the first place.

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

Plus, unless I'm mistaken, calling the destructor manually is the only option you have if you used placement new to call the constructor.

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

#10


Why destroy it at all? Just write over the memory? Are you wanting logic to execute to gracefully handle releasing resources? I am going to state emphatically that this is an abuse of the language and not a good idea.

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

#11


I would consider overriding new for the objects you want special allocation and deallocation behaviour for - after all that's what it's for. A hybrid scheme of normal new and explicitly calling destructors sounds like a recipe for future headaches. For starters, any memory leak detection strategy is going to get thrown way off.

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