Guaranteed cleanup with the destructor
As a C programmer, you often think about
the importance of initialization, but it’s rarer to think about cleanup.
After all, what do you need to do to clean up an int? Just forget about
it. However, with libraries, just “letting go” of an object once
you’re done with it is not so safe. What if it modifies some piece of
hardware, or puts something on the screen, or allocates storage on the heap? If
you just forget about it, your object never achieves closure upon its exit from
this world. In C++, cleanup is as important as initialization and is therefore
guaranteed with the
destructor.
The syntax for the destructor is similar
to that for the constructor: the class name is used for the name of the
function. However, the destructor is distinguished from the constructor by a
leading tilde (~). In addition, the destructor never has any arguments
because destruction never needs any options.
Here’s the declaration for a destructor:
class Y {
public:
~Y();
};
The destructor is called automatically by
the compiler when the object goes out of
scope. You can see where the
constructor gets called by the point of definition of the object, but the only
evidence for a destructor call is the closing brace of the scope that surrounds
the object. Yet the destructor is still called, even when you use
goto to jump out of a
scope. (goto still exists in C++ for backward compatibility with C and
for the times when it comes in handy.) You should note that a nonlocal
goto, implemented by the
Standard C library functions setjmp( ) and
longjmp( ), doesn’t cause destructors
to be called. (This is the specification, even if your compiler doesn’t
implement it that way. Relying on a feature that isn’t in the
specification means your code is nonportable.)
Here’s an example demonstrating the
features of constructors and destructors you’ve seen so
far:
//: C06:Constructor1.cpp
// Constructors & destructors
#include <iostream>
using namespace std;
class Tree {
int height;
public:
Tree(int initialHeight); // Constructor
~Tree(); // Destructor
void grow(int years);
void printsize();
};
Tree::Tree(int initialHeight) {
height = initialHeight;
}
Tree::~Tree() {
cout << "inside Tree destructor" << endl;
printsize();
}
void Tree::grow(int years) {
height += years;
}
void Tree::printsize() {
cout << "Tree height is " << height << endl;
}
int main() {
cout << "before opening brace" << endl;
{
Tree t(12);
cout << "after Tree creation" << endl;
t.printsize();
t.grow(4);
cout << "before closing brace" << endl;
}
cout << "after closing brace" << endl;
} ///:~
Here’s the output of the above
program:
before opening brace
after Tree creation
Tree height is 12
before closing brace
inside Tree destructor
Tree height is 16
after closing brace
You can see that the destructor is
automatically called at the closing brace of the scope that encloses
it.