What?
C++ was a boon for technical interviewers. There was a time when C++ was about the only viable object-oriented programming language in a business environment. In those days you pretty much had to know C/C++ to be an OO programmer. So you could test someone’s depth of C++ knowledge (and thus their overall suitability) by just asking about all the messed up edge cases that make C++ really tricky to use.
Today we’ll be observing a hypothetical candidate Phil. Phil has been a C++ programmer for 5 years (2 of which were at University) and has rated himself 9/10 on the C++ confidence scale that I asked him about at the beginning. I’ve been talking to Phil about the various ‘overloaded’ meanings of the keyword virtual in C++. Phil has correctly answered what a virtual method was, but couldn’t get the one about the virtual base class.
Even so, Phil is now into his stride and feeling pretty good about himself, so then I lay it on him:
Phil, is it possible to ever call a pure virtual method?
To which Phil (and about 95% of other candidates) would heartily reply “No”. And of course Phil would be wrong. But that’s ok. First I want Phil to tell me why a pure virtual method can not be called usually. This demonstrates a basic understanding, and then I would try and probe to see if Phil can guess the correct answer as to how a pure virtual method can be called and then the reason it’s possible.
So, what?
If Phil didn’t get the answer straight away then Phil probably won’t get it at all. This is not terribly surprising I suppose, and it’s not a reason to reject Phil. The sad truth about being a C++ programmer is that you really need to know how C++ works, at some level, to be able to use it effectively. I’m here to find out what Phil’s level really is. The more oppurtunities I give him to tell me about those edge cases the better I can assess how good a (C++) programmer he is.
How?
So here is a code sample that under a couple of compilers (VC++ 2005 – Express, and GCC 3.4.4) produces the (un?)desired behaviour. I wanted to try it under some more recent C++ compiler invocations but don’t have them available on the machine that I’m writing this. Anyway, these two compilers seem fairly representative of what a lot of people are currently using.
#include<stdio.h>
class A {
protected:
virtual void Reset() = 0;
void Init() {
Reset();
}
public:
A() {
Init();
}
};
class B : A {
protected:
void Reset() {
printf("Reset");
}
public:
B() {
}
};
int main(int argc, char** argv)
{
B b;
}
And here is the result:
$ g++ pvirt.cpp
$ ./a.out
pure virtual method called
9 [sig] a.out 180 _cygtls::handle_exceptions: Error while dumping state (p
robably corrupted stack)
Segmentation fault (core dumped)
So for those, like Phil, not fully aware of the vaguaries of calling pure virtual methods here is the explanation.
Pure virtual methods can be called from base-class constructors that attempt to indirectly call pure virtual methods.
The reason it's a problem is that C++ constructs objects inside-out, so base classes get built before derived classes. If you then try and call a virtual method on a partially constructed object you will end up calling the base class copy of the virtual method. This is because at the time you are in the base-class constructor the object is of type base-class and not of type derived-class. Calling pure virtual methods from constructors sounds like a really bad idea then, and it is. But there might be legitimate reasons to want to do it, which I'll come to in a minute.
Note that if you try and call the virtual method directly from the constructor, like so:
class A
{
protected:
virtual void Reset() = 0;
public:
A() {
Reset();
}
};
You will probably get a linker error because Reset() is pure and has no implementation, as you'd expect.
Why?
So why might you want to do this at all then? Well say you have a class hierarchy of objects that connect to a database for example.
So you dutifully create a Database base class with MySQLDatabase and OracleDatabase derived classes. Now when you disconnect an already established connection you need to reset all the structures as if they had just been created. The object is then ready to be reconnected. So you create a virtual Reset() method in the base class that does this. At the base level though this class should do nothing and you decide that your derived classes must define such a method so you very cleverly make it pure. You could then (but you probably wouldn't) write code like this:
void Database::Disconnect() {
Reset();
}
And have the derived class handle the gorey details of the reset. You want the same behaviour when you create a new object so you simply add the Reset() function to the Init() function that you created to deal with the fact that you had to write so many different types of copy constructor in C++! Hey presto pure virtual method called.
Note that if you had decided not to make the method pure and just put in an empty stub then this empty stub would be called instead (and not the derived version). In some ways this is a harder error to track because there is no error just a silent refusal by the object system to call the derived version because it can't, because it doesn't exist yet.
The correct way to deal with this, unsurprisingly, is to not call Reset() from the base-class Init() function but from the derived constructor directly.
Phil
Thanks for coming Phil, we'll be in touch.