Writing a Safer Deconstructor: Virtual Destructor in Polymorphism
When you use polymorphic objects, the base class must have virtual deconstructor, or it may cause memory leak.
Look the example:
#include <iostream>
using std::cout;
using std::endl;
void Debug(const char* str)
{
cout << str << endl;
}
class Animal
{
public:
Animal()
{
Debug("Animal()");
};
~Animal()
{
Debug("~Animal()");
};
};
class Cat : public Animal
{
public:
Cat()
{
Debug("Cat()");
};
~Cat()
{
Debug("~Cat()");
};
};
int main() {
Debug("-- Round 1 start --");
{
Cat c;
}
Debug("-- Round 1 End --");
Debug("-- Round 2 start --");
{
Animal *a = new Cat();
delete a;
}
Debug("-- Round 2 End --");
return 0;
}
The output is:
-- Round 1 start --
Animal()
Cat()
~Cat()
~Animal()
-- Round 1 End --
-- Round 2 start --
Animal()
Cat()
~Animal()
-- Round 2 End --
It may cause memory leak!
The object hold by Cat
will not be released
if ~Cat
isn't called.
It will be clearer when we add a member object in Cat
:
#include <iostream>
using std::cout;
using std::endl;
void Debug(const char* str)
{
cout << str << endl;
}
class Member
{
public:
Member()
{
Debug("Member()");
}
~Member()
{
Debug("~Member()");
}
};
class Animal
{
public:
Animal()
{
Debug("Animal()");
};
~Animal()
{
Debug("~Animal()");
};
};
class Cat : public Animal
{
public:
Cat()
{
Debug("Cat()");
};
~Cat()
{
Debug("~Cat()");
};
private:
Member m;
};
int main() {
Debug("-- Round 1 start --");
{
Cat c;
}
Debug("-- Round 1 End --");
Debug("-- Round 2 start --");
{
Animal *a = new Cat();
delete a;
}
Debug("-- Round 2 End --");
return 0;
}
The result is:
-- Round 1 start --
Animal()
Member()
Cat()
~Cat()
~Member()
~Animal()
-- Round 1 End --
-- Round 2 start --
Animal()
Member()
Cat()
~Animal()
-- Round 2 End --
You can see that no ~Member()
is called.
Thus, the memory occupied by Member m
is leaked!
Virtual Deconstructor
The solution is easy.
Simply adding a virtual
before ~Animal
can solve the problem.
...
...
class Animal
{
public:
Animal()
{
Debug("Animal()");
};
virtual ~Animal()
{
Debug("~Animal()");
};
};
...
...
The output will be
-- Round 1 start --
Animal()
Member()
Cat()
~Cat()
~Member()
~Animal()
-- Round 1 End --
-- Round 2 start --
Animal()
Member()
Cat()
~Cat()
~Member()
~Animal()
-- Round 2 End --
The ~Cat()
and ~Member()
will both be called,
so there is no leak problem anymore!
Conclusion
Never inherit a class that has no virtual deconstructor.