https://docs.microsoft.com/en-us/cpp/cpp/destructors-cpp?view=vs-2019
析构操作是一个对象的成员函数,在该对象离开作用域或通过delete操作明确的销毁时,被自动调用。析构操作与类同名,前面加个波浪符 (~)。例如,String类的析构操作的声明是:~String()。
当没有定义析构操作时,编译器会自动生成一个默认的;很多情况下,它是足够的。当类中占用了需要释放的系统资源或者它们拥有的指针占用着内存。
看看如下的一个String类:
// spec1_destructors.cpp
#include
class String {
public:
String( char *ch ); // Declare constructor
~String(); // and destructor.
private:
char *_text;
size_t sizeOfText;
};
// Define the constructor.
String::String( char *ch ) {
sizeOfText = strlen( ch ) + 1;
// Dynamically allocate the correct amount of memory.
_text = new char[ sizeOfText ];
// If the allocation succeeds, copy the initialization string.
if( _text )
strcpy_s( _text, sizeOfText, ch );
}
// Define the destructor.
String::~String() {
// Deallocate the memory that was previously reserved
// for this string.
delete[] _text;
}
int main() {
String str("The piper in the glen...");
}
在上面的类String中的析构操作 String::~String使用了delete操作符来释放动态分配的内存。
析构操作与类同名,前面加个波浪符 (~)。
析构操作的声明有几条基本规则:
如下几个事件之一发生时就会调用析构操作:
析构操作有两个限制:
当一个对象要被释放时,它的完整的析构操作的事件序列如下:
举例如下:
// order_of_destruction.cpp
#include
struct A1 { virtual ~A1() { printf("A1 dtor\n"); } };
struct A2 : A1 { virtual ~A2() { printf("A2 dtor\n"); } };
struct A3 : A2 { virtual ~A3() { printf("A3 dtor\n"); } };
struct B1 { ~B1() { printf("B1 dtor\n"); } };
struct B2 : B1 { ~B2() { printf("B2 dtor\n"); } };
struct B3 : B2 { ~B3() { printf("B3 dtor\n"); } };
int main() {
A1 * a = new A3;
delete a;
printf("\n");
B1 * b = new B3;
delete b;
printf("\n");
B3 * b2 = new B3;
delete b2;
}
Output: A3 dtor
A2 dtor
A1 dtor
B1 dtor
B3 dtor
B2 dtor
B1 dtor
对于“virtual base classes ”的析构操作按照声明(有向无环图)的反方向顺序执行,规则是“深度优先,从左到右,后序遍历”。下面的图描述了继承关系。
下面显示了声明关系:
class A
class B
class C : virtual public A, virtual public B
class D : virtual public A, virtual public B
class E : public C, public D, virtual public B
为了决定一个E类型的对象的析构操作顺序,编译器通过如下算法建立一个列表:
因此,对于类E,list的排序是:
The virtual base class A.
The virtual base class B.
The non-virtual base class C.
The non-virtual base class D.
The non-virtual base class E.
析构操作顺序为:
The non-virtual base class E.
The non-virtual base class D.
The non-virtual base class C.
The virtual base class B.
The virtual base class A
简单描述一下该list的构造过程。
1)首先定位到点E。
2)根据最左边优先遍历,定位到点A。记录下A节点。
3)A的上一个节点是C不是虚拟基类,忽略。
4)把A记录到list中
5)向C的另一个上侧路径遍历,定位到B。。记录下B节点。
6)B的上一个节点是C不是虚拟基类,忽略。
7)把B记录到list中
8)节点C向上的路径都穷尽了,记录C节点。
9)访问D节点,D不是虚拟基类,忽略。
10)把C记录到list中
10)把D记录到list中
这种在一个继承关系图中存在类之间有相互依赖关系是很危险的。因为后面的子类会改变哪个是最左边路径,因此,它们也会改变构造和析构的顺序。
The destructors for non-virtual base classes are called in the reverse order in which the base class names are declared. Consider the following class declaration:
class MultInherit : public Base1, public Base2
...
In the preceding example, the destructor for Base2 is called before the destructor for Base1.
Calling a destructor explicitly is seldom necessary. However, it can be useful to perform cleanup of objects placed at absolute addresses. These objects are commonly allocated using a user-defined new operator that takes a placement argument. The delete operator cannot deallocate this memory because it is not allocated from the free store (for more information, see The new and delete Operators). A call to the destructor, however, can perform appropriate cleanup. To explicitly call the destructor for an object, s, of class String, use one of the following statements:
s.String::~String(); // non-virtual call
ps->String::~String(); // non-virtual call
s.~String(); // Virtual call
ps->~String(); // Virtual call
The notation for explicit calls to destructors, shown in the preceding, can be used regardless of whether the type defines a destructor. This allows you to make such explicit calls without knowing if a destructor is defined for the type. An explicit call to a destructor where none is defined has no effect.
A class needs a destructor if it acquires a resource, and to manage the resource safely it probably has to implement a copy constructor and a copy assignment.
If these special functions are not defined by the user, they are implicitly defined by the compiler. The implicitly generated constructors and assignment operators perform shallow, memberwise copy, which is almost certainly wrong if an object is managing a resource.
In the next example, the implicitly generated copy constructor will make the pointers str1.text and str2.text refer to the same memory, and when we return from copy_strings(), that memory will be deleted twice, which is undefined behavior:
void copy_strings()
{
String str1("I have a sense of impending disaster...");
String str2 = str1; // str1.text and str2.text now refer to the same object
} // delete[] _text; deallocates the same memory twice
// undefined behavior
Explicitly defining a destructor, copy constructor, or copy assignment operator prevents implicit definition of the move constructor and the move assignment operator. In this case, failing to provide move operations is usually, if copying is expensive, a missed optimization opportunity.