手动调用析构函数总是一个糟糕的设计标志?

Vio*_*ffe 73 c++ destructor coding-style

我在想:他们说如果你手动调用析构函数 - 你做错了什么.但情况总是这样吗?有反例吗?有必要手动调用它或者难以避免的情况/不可能/不切实际的情况吗?

Emi*_*lia 94

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

每次需要销毁对象(在C++意义上)而不释放对象所在的内存时,都会显式调用dtor .

这通常发生在独立于对象构造/销毁的情况下管理内存分配/解除分配的所有情况.在这些情况下,构造通过在存在的大块内存上放置new来进行,并且通过显式dtor调用进行破坏.

这是原始示例:

{
  char buffer[sizeof(MyClass)];

  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }
  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }

}
Run Code Online (Sandbox Code Playgroud)

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

这是"糟糕的设计",在严格的OOP意义上(你应该管理对象,而不是内存的事实:对象需要的内存是一个"事件"),这是"好设计",在"低层次的编程",或在内存的情况下没有取自默认operator new购买的"免费商店" .

如果它在代码周围随机发生,这是一个糟糕的设计,如果它本地发生在专门为此目的而设计的类中,那么这是一个很好的设计.

  • 只是好奇为什么这不是公认的答案. (8认同)

Die*_*ühl 85

如果使用重载形式构造对象,则需要手动调用析构函数operator new(),除非使用" 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);
Run Code Online (Sandbox Code Playgroud)

然而,外部管理内存的处于相当低的水平,如上所述明确地调用析构函数,这设计糟糕的标志.实际上,它实际上不仅仅是糟糕的设计,而且是完全错误的(是的,使用显式析构函数后跟赋值运算符中的复制构造函数调用一个糟糕的设计,可能是错误的).

使用C++ 2011时,还有另一个使用显式析构函数调用的原因:使用通用联合时,在更改所表示对象的类型时,必须使用placement new显式销毁当前对象并创建新对象.此外,当联合被销毁时,如果需要销毁,则必须显式调用当前对象的析构函数.

  • 而不是说"使用'operator new`'的重载形式",正确的短语是"使用`placement new`". (21认同)
  • @RemyLebeau:嗯,我想澄清一点,我不仅仅讨论`operator new(std :: size_t,void*)`(以及数组变体),而是讨论`operator new()的所有重载版本. . (5认同)

Jac*_*ack 10

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

例如.

{
  Class c;
  c.~Class();
}
Run Code Online (Sandbox Code Playgroud)

如果您确实需要执行相同的操作,则应该使用单独的方法.

在某种特定情况下,您可能希望在具有放置位置的动态分配对象上调用析构函数,new但这听起来并不是您需要的.


小智 10

不,取决于情况,有时它是合法和良好的设计.

要了解为什么以及何时需要明确调用析构函数,让我们看看"新"和"删除"发生了什么.

要动态创建一个对象,需要 T* t = new T;:1.分配sizeof(T)内存.2.调用T的构造函数来初始化分配的内存.operator new执行两项操作:分配和初始化.

摧毁delete t;引擎盖下的物体:1.调用T的析构函数.2.释放为该对象分配的内存.operator delete也做了两件事:破坏和释放.

一个写入构造函数进行初始化,而析构函数进行破坏.显式调用析构函数时,只执行销毁,但不执行解除分配.

因此,显式调用析构函数的合法用法可能是,"我只想破坏对象,但我不(或不能)释放内存分配(但)."

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

创建新对象时,您将从预先分配的池中获取内存块并执行"placement new".完成对象后,您可能希望显式调用析构函数来完成清理工作(如果有).但是你实际上不会释放内存,因为操作符删除会完成.而是将块返回池中以供重用.


Luc*_*ore 8

如FAQ所引用,您应该在使用placement new时显式调用析构函数.

这是你唯一一次明确调用析构函数的时间.

我同意,但这很少需要.


Jam*_*nze 5

每当需要将分配与初始化分开时,都需要手动放置析构函数的新的和显式的调用。如今,由于我们拥有标准容器,因此几乎没有必要,但是如果您必须实现某种新型的容器,则将需要它。