手动调用析构函数始终是坏devise的标志?

我在想:他们说如果你手动调用析构函数 – 你做错了什么。 但是情况总是如此吗? 有任何反驳吗? 情况需要手动调用,哪里难以避免?不可能/不可行/不切实际。

如果使用operator new()的重载forms构造对象,则需要手动调用析构函数,除非使用“ std::nothrow ”重载:

 T* t0 = new(std::nothrow) T(); delete t0; // OK: std::nothrow overload void* buffer = malloc(sizeof(T)); T* t1 = new(buffer) T(); t1->~T(); // required: delete t1 would be wrong free(buffer); 

但是,如上所述,在外部pipe理内存的时候,明确地调用了析构函数,这devise不好的一个标志。 可能它实际上不仅仅是糟糕的devise,而是完全错误的(是的,在赋值运算符中使用显式的析构函数,然后是复制构造函数调用一个糟糕的devise,可能是错误的)。

使用C ++ 2011还有另一个使用显式析构函数的原因:当使用泛化联合时,需要显式销毁当前对象,并在更改所表示对象的types时使用新build位置创build新对象。 另外,当联合被销毁时,如果需要销毁,就必须显式调用当前对象的析构函数。

所有的答案都描述了具体的案例,但有一个一般的答案:

每当你需要在不释放对象所在的内存的情况下销毁对象 (以C ++的方式)的时候,你明确地调用了dtor。

这通常发生在内存分配/释放独立于对象构造/销毁而被pipe理的所有情况下。 在这种情况下,通过在现有的大量内存上进行重新布局来实现,并且通过显式的dtor调用来发生破坏。

这里是原始的例子:

 { char buffer[sizeof(MyClass)]; { MyClass* p = new(buffer)MyClass; p->dosomething(); p->~MyClass(); } { MyClass* p = new(buffer)MyClass; p->dosomething(); p->~MyClass(); } } 

另外一个值得注意的例子是当std::vector使用默认的std::allocator时:元素是在push_back期间在vector中构造的,但是内存是以块分配的,所以它预先存在元素构造。 因此, vector::erase必须销毁这些元素,但不一定会释放内存(特别是如果新的push_back很快就会发生的话)。

这是严格的OOP意义上的“坏devise”(你应该pipe理对象,而不是内存:事实对象要求内存是一个“事件”),在“低级编程”中是“好devise”,或者在内存不是从“免费店铺”购买的默认operator new

如果它在代码中随机发生,这是不好的devise,如果它本地发生在专门为此目的而devise的类上,那么这是很好的devise。

正如FAQ所引用的, 当使用placement new时 , 应该明确调用析构函数 。

这是你唯一一次显式调用析构函数的时间。

我同意,这是很less需要的。

不,你不应该明确地调用它,因为它会被调用两次。 一次用于手动调用,另一次是声明对象的范围结束。

例如。

 { Class c; c.~Class(); } 

如果你真的需要执行相同的操作,你应该有一个单独的方法。

有一个特定的情况 ,你可能想要在一个dynamic分配的对象上调用一个析构函数,但它听起来并不是你所需要的。

不,取决于情况,有时候是合法的, 好的devise。

要理解为什么以及何时需要明确调用析构函数,我们来看看“new”和“delete”的情况。

为了dynamic创build一个对象, T* t = new T; 引擎盖下:1. sizeof(T)内存分配。 2. T的构造函数被调用来初始化分配的内存。 新运营商做两件事:分配和初始化。

delete t;对象delete t; 引擎盖下:1.T的析构函数被调用。 2.为该对象分配的内存被释放。 运算符删除也做两件事情:销毁和释放。

一个写构造函数做初始化,并且析构函数做破坏。 当你明确地调用析构函数时,只有破坏已经完成,而不是解除分配

因此,明确调用析构函数的合法用法可能是“我只想破坏对象,但我不(或不能)释放内存分配(还)”。

一个常见的例子就是预先为某些对象池分配内存,否则这些对象必须dynamic分配。

当创build一个新的对象时,你从预先分配的池中获得大块内存,并做一个“放置新的”。 完成对象之后,可能需要显式调用析构函数来完成清理工作(如果有的话)。 但是你不会真的释放内存,因为操作符删除会完成。 相反,您将块返回到池以供重用。

任何时候你需要从初始化分离分配,你需要手动地安置新的和显式的析构函数。 今天,很less有必要,因为我们有标准的容器,但是如果你必须实现一些新的容器,你就需要它。

有些情况下,他们是必要的:

在代码我工作我​​在分配器中使用显式的析构函数调用,我已经实现了简单的分配器,使用新的位置将内存块返回到STL容器。 在摧毁我有:

  void destroy (pointer p) { // destroy objects by calling their destructor p->~T(); } 

而在构build:

  void construct (pointer p, const T& value) { // initialize memory with placement new #undef new ::new((PVOID)p) T(value); } 

allocate()和deallocate()中的内存释放也是通过使用特定于平台的alloc和dealloc机制来完成的。 这个分配器被用来绕过doug lea malloc,直接在窗口上使用LocalAlloc。

我从来没有遇到过需要手动调用析构函数的情况。 我似乎记得甚至Stroustrup声称这是不好的做法。

我发现有三次我需要这样做:

  • 在由内存映射IO或共享内存创build的内存中分配/解除分配对象
  • 当使用C ++实现给定的C接口(是的,今天仍然不幸发生(因为我没有足够的影响力来改变它))
  • 当实现分配器类

那这个呢?
如果从构造函数中引发exception,则不调用析构函数,所以我必须手动调用它来销毁在exception之前在构造函数中创build的句柄。

 class MyClass { HANDLE h1,h2; public: MyClass() { // handles have to be created first h1=SomeAPIToCreateA(); h2=SomeAPIToCreateB(); ... try { if(error) { throw MyException(); } } catch(...) { this->~MyClass(); throw; } } ~MyClass() { SomeAPIToDestroyA(h1); SomeAPIToDestroyB(h2); } }; 

我有另一种情况,我认为调用析构函数是完全合理的。

当编写一个“重置”types的方法来将对象恢复到初始状态时,调用析构函数删除正在重置的旧数据是完全合理的。

 class Widget { private: char* pDataText { NULL }; int idNumber { 0 }; public: void Setup() { pDataText = new char[100]; } ~Widget() { delete pDataText; } void Reset() { Widget blankWidget; this->~Widget(); // Manually delete the current object using the dtor *this = blankObject; // Copy a blank object to the this-object. } }; 

记忆与其他资源没有什么不同:你应该看看http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style,特别是Bjarne谈到RAII的部分(约〜30分钟);

所有必需的模板(shared_ptr,unique_ptr,weak_ptr)都是C ++ 11标准库的一部分