C++ 手动调用析构函数总是不好设计的标志吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14187006/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 18:05:56  来源:igfitidea点击:

Is calling destructor manually always a sign of bad design?

c++coding-styledestructor

提问by Violet Giraffe

I was thinking: they say if you're calling destructor manually - you're doing something wrong. But is it always the case? Are there any counter-examples? Situations where it is neccessary to call it manually or where it is hard/impossible/impractical to avoid it?

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

回答by Emilio Garavaglia

All answers describe specific cases, but there is a general answer:

所有答案都描述了特定情况,但有一个通用答案:

You call the dtor explicitly every time you need to just destroy the object(in C++ sense) without releasing the memorythe object resides in.

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

This typically happens in all the situation where memory allocation / deallocation is managed independently from object construction / destruction. In those cases construction happens via placement newupon an existent chunk of memory, and destruction happens via explicit dtor call.

这通常发生在内存分配/释放与对象构造/销毁独立管理的所有情况下。在这些情况下,构造是通过在现有内存块上放置 new来发生的,而销毁是通过显式 dtor 调用发生的。

Here is the raw example:

这是原始示例:

{
  char buffer[sizeof(MyClass)];

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

}

Another notable example is the default std::allocatorwhen used by std::vector: elements are constructed in vectorduring push_back, but the memory is allocated in chunks, so it pre-exist the element contruction. And hence, vector::erasemust destroy the elements, but not necessarily it deallocates the memory (especially if new push_back have to happen soon...).

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

It is "bad design" in strict OOP sense (you should manage objects, not memory: the fact objects require memory is an "incident"), it is "good design" in "low level programming", or in cases where memory is not taken from the "free store" the default operator newbuys in.

在严格的 OOP 意义上,这是“糟糕的设计”(您应该管理对象,而不是内存:对象需要内存的事实是“事件”),这是“低级编程”中的“好设计”,或者在内存不足的情况下不是从默认operator new购买的“免费商店”中获取的。

It is bad design if it happens randomly around the code, it is good design if it happens locally to classes specifically designed for that purpose.

如果它在代码周围随机发生,则是糟糕的设计,如果它发生在专门为此目的而设计的类的本地,则是好的设计。

回答by Dietmar Kühl

Calling the destructor manually is required if the object was constructed using an overloaded form of operator new(), except when using the "std::nothrow" overloads:

如果对象是使用 的重载形式构造的,则需要手动调用析构函数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);

Outside managing memory on a rather low level as above calling destructors explicitly, however, isa sign of bad design. Probably, it is actually not just bad design but outright wrong (yes, using an explicit destructor followed by a copy constructor call in the assignment operator isa bad design and likely to be wrong).

然而,在相当低的级别上管理内存之外,显式调用析构函数设计不良的标志。可能,它实际上不仅是糟糕的设计,而且是完全错误的(是的,在赋值运算符中使用显式析构函数后跟复制构造函数调用一种糟糕的设计,并且可能是错误的)。

With C++ 2011 there is another reason to use explicit destructor calls: When using generalized unions, it is necessary to explicitly destroy the current object and create a new object using placement new when changing the type of the represented object. Also, when the union is destroyed, it is necessary to explicitly call the destructor of the current object if it requires destruction.

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

回答by Hyman

No, you shouldn't call it explicitly because it would be called twice. Once for the manual call and another time when the scope in which the object is declared ends.

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

Eg.

例如。

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

If you really need to perform the same operations you should have a separate method.

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

There is a specific situationin which you may want to call a destructor on a dynamically allocated object with a placement newbut it doesn't sound something you will ever need.

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

回答by Hyman

No, Depends on the situation, sometimes it is legitimate and gooddesign.

不,视情况而定,有时它是合法且良好的设计。

To understand why and when you need to call destructors explicitly, let's look at what happening with "new" and "delete".

要了解为什么以及何时需要显式调用析构函数,让我们看看“new”和“delete”发生了什么。

To created an object dynamically, T* t = new T;under the hood: 1. sizeof(T) memory is allocated. 2. T's constructor is called to initialize the allocated memory. The operator new does two things: allocation and initialization.

为了动态创建一个对象, T* t = new T;引擎盖下: 1. sizeof(T) 内存被分配。2. 调用T的构造函数来初始化分配的内存。操作符 new 做两件事:分配和初始化。

To destroy the object delete t;under the hood: 1. T's destructor is called. 2. memory allocated for that object is released. the operator delete also does two things: destruction and deallocation.

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

One writes the constructor to do initialization, and destructor to do destruction. When you explicitly call the destructor, only the destruction is done, but not the deallocation.

一种是写构造函数来做初始化,而析构函数来做析构。当您显式调用析构函数时,只会完成销毁,而不会完成解除分配

A legitimate use of explicitly calling destructor, therefore, could be, "I only want to destruct the object, but I don't (or can't) release the memory allocation (yet)."

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

A common example of this, is pre-allocating memory for a pool of certain objects which otherwise have to be allocated dynamically.

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

When creating a new object, you get the chunk of memory from the pre-allocated pool and do a "placement new". After done with the object, you may want to explicitly call the destructor to finish the cleanup work, if any. But you won't actually deallocate the memory, as the operator delete would have done. Instead, you return the chunk to the pool for reuse.

创建新对象时,您会从预先分配的池中获取内存块并进行“放置新”。处理完对象后,您可能希望显式调用析构函数以完成清理工作(如果有)。但是您实际上不会像操作符 delete 那样释放内存。相反,您将块返回到池中以供重用。

回答by Luchian Grigore

As quoted by the FAQ, you should call the destructor explicitly when using placement new.

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

This is about the only time you ever explicitly call a destructor.

这是您唯一一次明确调用析构函数。

I agree though that this is seldom needed.

我同意虽然这很少需要。

回答by James Kanze

Any time you need to separate allocation from initialization, you'll need placement new and explicit calling of the destructor manually. Today, it's rarely necessary, since we have the standard containers, but if you have to implement some new sort of container, you'll need it.

任何时候您需要将分配与初始化分开,您都需要手动放置新的并显式调用析构函数。今天,它很少有必要,因为我们有标准容器,但是如果您必须实现某种新的容器,您将需要它。

回答by marcinj

There are cases when they are necessary:

在某些情况下,它们是必要的:

In code I work on I use explicit destructor call in allocators, I have implementation of simple allocator that uses placement new to return memory blocks to stl containers. In destroy I have:

在我处理的代码中,我在分配器中使用显式析构函数调用,我实现了简单的分配器,该分配器使用放置 new 将内存块返回到 stl 容器。在摧毁我有:

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

while in construct:

在构造时:

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

there is also allocation being done in allocate() and memory deallocation in deallocate(), using platform specific alloc and dealloc mechanisms. This allocator was used to bypass doug lea malloc and use directly for example LocalAlloc on windows.

使用特定于平台的 alloc 和 dealloc 机制,allocate() 中的分配和 deallocate() 中的内存释放也在完成。该分配器用于绕过 doug lea malloc 并直接使用,例如在 Windows 上使用 LocalAlloc。

回答by Lieuwe

I have never come across a situation where one needs to call a destructor manually. I seem to remember even Stroustrup claims it is bad practice.

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

回答by Lieuwe

I found 3 occasions where I needed to do this:

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

  • allocating/deallocating objects in memory created by memory-mapped-io or shared memory
  • when implementing a given C interface using C++ (yes this still happens today unfortunately (because I don't have enough clout to change it))
  • when implementing allocator classes
  • 在内存映射 io 或共享内存创建的内存中分配/释放对象
  • 使用 C++ 实现给定的 C 接口时(是的,不幸的是,今天仍然会发生这种情况(因为我没有足够的影响力来改变它))
  • 实现分配器类时

回答by CITBL

What about this?
Destructor is not called if an exception is thrown from the constructor, so I have to call it manually to destroy handles that have been created in the constructor before the exception.

那这个呢?
如果从构造函数抛出异常,则不会调用析构函数,因此我必须手动调用它以销毁在异常发生之前在构造函数中创建的句柄。

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);
  }
};