C++ is one of the most popular programming languages which is being used in multiple domains. Its diverse use means it supports many programming paradigms. One of the most important features of C++ is the support of Object-Oriented Programming, also known as OOP. Most of the textbooks available in C++ do not go into detail about how C++ objects are being managed internally. This has created many misconceptions about many features of C++. In our last article, we've tried to answer a few misconceptions of new C++ developers, on when compilers synthesize a constructor aka default constructor.
Similarly, new C++ engineers have misconceptions about destructors i.e.
In absence of a user-defined destructor in a class, compiler synthesizes destructor...
When does the compiler synthesize a destructor?
For someone new to the C++ language, the below class Foo (example 1) may give the impression that the compiler will generate a constructor and destructor in absence of a user-provided destructor.
// example 1
class Foo
{
public:
int val;
int* ptr;
};
void perfrom_action()
{
int* lptr;
{// bar object scope start
Foo bar;
// program needs bar's members zeroed out
if ( bar.val || bar.ptr )
; // ... perform some action
bar.ptr=new int(222); // do something with Foo::ptr
lptr=bar.ptr; // assign Foo::ptr to local pointer
}// bar object scope end
std::cout<<"value inlptr: "<<*lptr<<'\n'; //Foo::ptr is valid on heap
}
If the user has not defined a destructor in a class then the destructor is synthesized only if implementation requires. This means in absence of a destructor in the class, the compiler synthesizes the destructor only if the class contains either member or base class with destructor. In other cases, the destructor is considered to be trivial and therefore neither synthesized nor invoked. This is similar to what we've seen in our earlier article wrt constructor.
Let's try to understand this with the help of a simple example. To understand this we will use the below Point2D class and Line class.
// example 2
class Point2D
{
public:
Point( float px = 0.0, float py = 0.0 ); // User defined constructor
Point( const Point& ); // Copy constructor
virtual float z();
//...
private:
float x, y;
};
In the above Point2D class we've not defined a destructor and in this case, the compiler will not generate a destructor even though we've virtual function z(). Similarly, when we compose class Line using Point2D, the compiler will not synthesize destructor.
// example 3
class Line
{
public:
Line( const Point2D&, const Point2D& );
//...
virtual draw();
protected:
Point2D begin, end;
};
In the above Line class, the compiler will not synthesize destructor as member object of Point2D does not have destructor defined which means there is no need to generate a default constructor by the compiler.
Similarly, if we create class Point3D by inheriting class Point2D by virtual derivation, the compiler will not synthesize the destructor in absence of a user-defined destructor.
// example 4
class Foo
{
public:
Foo():val{0},ptr{nullptr} {}
int val;
int* ptr;
~Foo()
{
if(ptr) delete ptr;
}
};
If we update the class Foo with a user-defined destructor then the compiler will synthesize the destructor for class Line. The pseudo-code looks something like below. Note, if the Line class holds any resource like heap-allocated memory, the synthesized destructor will not release those resources.
// example 5
// Pseudo code for Line's synthesized destructor
Line::~Line()
{
begin.Foo::~Foo();
end.Foo::~Foo();
}
Order of destruction
If we extend our example 2 derive another class called Point3D with a user-defined destructor and class Point3D further extended to class Vertex, which looks something like the below code snippet.
// example 6
class Point3D:: public Point2D
{
//...
~Point3D();
}
class Vertex:: public Point3D
{
//...
}
If we don't provide a destructor for the Vertex class then the compiler will synthesize the destructor for Vectex class, whose only job would be to call Point3D's destructor. In case if we do provide a destructor for Vertex::~Vertex() then the compiler will argument the destructor of Vertex with a call to Point3D's destructor after user-supplied code is executed. User-defined destructor is augmented in the same manner as in the case of constructors, except in the reverse order:
If the object contains a virtual pointer i.e. vptr, it is reset to the virtual table associated with the class.
The body of the destructor is then executed; i.e. the vptr is reset prior to evaluating the user-supplied code.
If the class has member class objects with destructors, these are invoked in the reverse order of their declaration.
If there are any immediate nonvirtual base classes with destructors, these are invoked in the reverse order of their declaration.
If there are any virtual base classes with destructors and this class represents the most-derived class, these are invoked in the reverse order of their original construction.
Comments