Destructor
Explore the role of destructors in D programming by understanding how they handle operations at the end of an object's lifetime. Learn to define destructors to manage resources and produce matched XML element tags using constructors and destructors. This lesson helps you grasp how automatic and custom destructors ensure proper cleanup and output formatting.
We'll cover the following...
Destructor definition
The destructor includes the operations that must be executed when the lifetime of an object ends.
The compiler-generated, automatic destructor executes the destructors of all of the members in order. For that reason, as it is with the constructor, there is no need to define a destructor for most structs.
However, sometimes some special operations may need to be executed when an object’s lifetime ends. For example, an operating system resource that the object owns may need to be returned to the system, a member function of another object may need to be called, a server running somewhere on the network may need to be notified that a connection to it is about to be terminated, etc.
The name of the destructor is
~this, and just like constructors, it has no return type.
Automatic destructor execution
The destructor is executed as soon as the lifetime of the struct object ends.
As you have seen in a previous chapter, the lifetime of an object ends when it leaves the scope that it is defined in. The lifetime of a struct type ends at the following times:
-
When leaving the scope of the object either normally or due to a thrown exception:
if (aCondition) { auto duration = Duration(7); // ... } // ← The destructor is executed for 'duration' // at this point -
Anonymous objects are destroyed at the end of the whole expression that they are constructed in:
time.increment(Duration(5)); // ← The Duration(5) object gets destroyed at the end of the whole expression. -
All of the struct members of a struct object get destroyed when the outer object is destroyed.
This is not the case for objects that are constructed with the
newkeyword.
Destructor example
Let’s design a type for generating simple XML documents. XML elements are defined by angle brackets. They contain data and other XML elements. XML elements can have attributes as well, but we will ignore them here.
Our aim will be to ensure that an element that has been opened by a < name > tag will always be closed by a matching < /name > tag:
<class1> ← opening the outer XML element
<grade> ← opening the inner XML element
57 ← the data
</grade> ← closing the inner element
</class1> ← closing the outer element
A struct that can produce the output above can be designed by two members that store the tag for the XML element and the indentation to use when printing it:
struct XmlElement {
string name;
string indentation;
}
If the responsibilities of opening and closing the XML element are given to the constructor and the destructor, respectively, the desired output can be produced by managing the lifetimes of the XmlElement objects. For example, the constructor can print < tag> and the destructor can print < /tag>.
The following definition of the constructor produces the opening tag:
this(string name, int level) {
this.name = name;
this.indentation = indentationString(level);
writeln(indentation, '<', name, '>');
}
indentationString() is the following function:
import std.array;
// ...
string indentationString(int level) {
return replicate(" ", level * 2);
}
The function calls replicate() from the std.array module, which makes and returns a new string made up of the specified value repeated the specified number of times.
The destructor can be defined similar to the constructor to produce the closing tag:
~this() {
writeln(indentation, "</", name, '>');
}
Here is a test code to demonstrate the effects of the automatic constructor and destructor calls:
Note that the XmlElement objects are created in three separate scopes in the program above (Line 26, 29, and 32). The opening and closing tags of the XML elements in the output are produced solely by the constructor and the destructor of XmlElement.
The < classes> element is produced by the classesElement variable. Because that variable is constructed first in main(), the output contains the output of its construction first. Since it is also the variable that is destroyed last, upon leaving main(), the output contains the output of the destructor call for its destruction last.