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

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

122542 次浏览

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 situation in which you may want to call a destructor on a dynamically allocated object with a placement new but it doesn't sound something you will ever need.

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.

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

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

I agree though that this is seldom needed.

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:

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, is a 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 is a 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.

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:

  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.

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.

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 memory the object resides in.

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 new upon an existent chunk of memory, and destruction happens via explicit dtor call.

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::allocator when used by std::vector: elements are constructed in vector during push_back, but the memory is allocated in chunks, so it pre-exist the element contruction. And hence, vector::erase must destroy the elements, but not necessarily it deallocates the memory (especially if new push_back have to happen soon...).

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 new buys in.

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.

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

To understand why and when you need to call destructors explicitly, let's look at what happening with "new" and "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.

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.

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.

I found 3 occasions where I needed to do this:

  • 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

I have another situation where I think it is perfectly reasonable to call the destructor.

When writing a "Reset" type of method to restore an object to its initial state, it is perfectly reasonable to call the Destructor to delete the old data that is being reset.

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

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

Found another example where you would have to call destructor(s) manually. Suppose you have implemented a variant-like class that holds one of several types of data:

struct Variant {
union {
std::string str;
int num;
bool b;
};
enum Type { Str, Int, Bool } type;
};

If the Variant instance was holding a std::string, and now you're assigning a different type to the union, you must destruct the std::string first. The compiler will not do that automatically.