Home > Rant > When destruction doesn’t mean destruction

When destruction doesn’t mean destruction

Disclaimer: This rant about C++, or rather about the compiler used for building zkanji is very technical. Non-programmers shouldn’t even try to understand it. Programmers, please don’t laugh. The explanations are probably not correct entirely, the rant is not an exercise in C++.

If you have ever used C++ you probably know what a virtual function is, but not everyone knows about virtual tables. (You can skip the following few paragraphs if you do – but be aware! They work differently in other languages!)

I won’t explain what objects and classes are, so if you don’t know that much, turn back now. When a class called B is derived from a base class called A, B can use the base class’ public and protected functions (in case when we are talking about public inheritance of course) . If A has a function called F taking no parameters, which is a “virtual” function, we can create our own F function in B too taking no parameters, making it virtual too. If an instance of B is created, then calling its F will call B::F(). When that instance is put in a variable of class A, calling the F for that will still call B::F(). (But for instance, if the F function was not virtual, calling F in the latter case would call A::F() instead.) Putting it all to code:

class A {
public:
A() { ; } // constructor, doing nothing
virtual ~A() { F1(); } // destructor. calls the virtual F1
virtual void F1() { /* do something */ ; }
void F2() { /* do something again */ ; }
};

class B : public A {
public:
B() : A() { ; } // constructor, doing nothing
virtual ~B() { /* do some stuff */ ; }
virtual void F1() { /* do something else */ ; }
void F2() { /* do something else again */ ; }
};

B *b = new B;
A *a = b;
b->F1(); // calls B::F1
b->F2(); // calls B::F2
a->F1(); // calls B::F1
a->F2(); // calls A::F2 because F2 is not virtual.

delete a; // deletes the instance of B in *a and calls the object’s destructors. Then F1() is called from ~A. (see above)

When first calling F1 of a, the B::F1() is called, because it is virtual. For this to work, the compiler creates a so-called virtual table for the object. It can look up the real type of a (which is B in this case) and call the correct function. When we destroy a, first the destructor of B, ~B is invoked, as it is virtual as well, and after that ~A is called automatically.  It then calls F1, but that is A::F1() in this case. In Standard C++, F1 will refer to A::F1() here. By the time ~A is reached, the virtual table for B does not exist anymore. In real programs B would do some cleanup, probably making B::F1() unsafe, which might operate on freed variables, thus being able to call that would result in some strange errors. That’s why C++ prevents us from making such a mistake.

Here comes the rant about what C++ Builder does differently…

The library used by C++ Builder, which makes it possible to easily manipulate on windows controls and other things was written in Delphi, which uses a modern Pascal variant. It also has virtual functions, but the virtual table in Pascal works a bit differently. When deleting an object in both languages, the destructor of the last derived class is called first, and the first base class’ the last. If the base class’ destructor called a virtual function in C++, only the function of the base class could be retrieved, as the virtual table for all derived classes are already freed up. On the other hand in Delphi’s Pascal, the whole virtual table exists, until the last destructor finishes its job. Calling a virtual function from a destructor in C++ is a bit risky if you don’t know what you are doing, but in Delphi’s Pascal it’s a suicide.

Unfortunately I had to face a little undocumented feature of C++ Builder. Once my class was derived from a base class of the original library (which was written in Pascal), the virtual table stopped working in a standard way. The virtual function of the derived class was called from the destructor of the base class, even though it is against the rules of standard C++. The result: for some reason nothing at all in previous versions of the compiler, but since I switched to be able to use Unicode, the generated code somehow started producing unexpected errors.

Thanks to this, I’m a few years older since last week

Advertisements
Categories: Rant Tags:
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: