Summary
In this post, I will introduce rules related to constructors and destructors of classes which has virtual functions.
Conclusions
Golden Rule 1: Define a virtual destructor immediately
Golden Rule 2: Do not invoke virtual functions from constructors or destructors
Details
In C++, Virtual functions allow for the choice of member function calls to be determined at run time based on the dynamic type of the object that the member function is being called on, which supports OOP practices commonly associated with object inheritance and function overriding.
Golden Rule 1
Firstly you should notice that in C++ Standard,
If a class has no user-declared destructor, a destructor
is implicitly declared as defaulted. An implicitly declared
destructor is an inline public member of its class.
In many design patterns, people will implement classes which are inherited from a virtual interface. Customers might use pointers of the interface which point to the derived instance. It comes to the problem.
class Base
{
// an interface consists of some virtual methods
};
class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};
When customers want to delete an instance of a derived class through a pointer to base class, if base’s destructor is not virtual, delete b
has undefined behavior. In most implementations, the call to the destructor will be resolved like any non-virtual code, meaning that the destructor of the base class will be called but not the one of the derived class, resulting in a resources leak.
Golden Rule 2
Calling virtual functions from a constructor or destructor is dangerous and should be avoided whenever possible. All C++ implementations should call the version of the function defined at the level of the hierarchy in the current constructor and no further.
class B {
public:
B(const string& ss) { cout << "B constructor\n"; f(ss); }
virtual void f(const string&) { cout << "B::f\n";}
};
class D : public B {
public:
D(const string & ss) :B(ss) { cout << "D constructor\n";}
void f(const string& ss) { cout << "D::f\n"; s = ss; }
private:
string s;
};
int main()
{
D d("Hello");
}
D attempts to overrides B’s f. And the programmer knows that the order of construction is from base to derived. He then naively thinks once f is overridden, B’s constructor will actually call D’s f. Which is great.
However, the result is
B constructor
B::f
D constructor
Then you may ask when my base class’s constructor calls a virtual function on its this object, why doesn’t my derived class’s override of that virtual function gets invoked. Because it is dangerous.
Consider what would happen if the rule were different so that D::f() was called from B::B(): Because the constructor D::D() hadn’t yet been run, D::f() would try to assign its argument to an uninitialized string s. The result would most likely be an immediate crash.
Luckily C++ is protecting you from this danger.
In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn’t yet happened. Objects are constructed from the base up, “base before derived”. So is the destructor.
The correct way is every class do its own things.
class B {
void seize_mine();
void release_mine();
public:
B() { seize_mine(); }
virtual ~B() { release_mine(); }
protected:
virtual void seize() { seize_mine(); }
virtual void release() { release_mine(); }
};
class D : public B {
void seize_mine();
void release_mine();
public:
D() { seize_mine(); }
virtual ~D() { release_mine(); }
protected:
void seize() override {
B::seize();
seize_mine();
}
void release() override {
release_mine();
B::release();
}
};
seize_mine
and release_mine
are overrided and each class has its own copy in each level.