Introduction
Experienced C++ programmers often have the habit of expecting that when something compiles, it is typesafe and it is therefore correct. Even though it may not always be true, this habit grows because it is very often true. A couple of days ago I ran into one of those situations where a compiling program, which I expected to work (because it compiled and it passed my testing) didn't do what I would have expected in a particular situation. I debugged it to find out what the problem was. It turned out that I had added a parameter to a member function of a certain class, but that I had forgotten to add that same parameter to a member function in a derived class that was supposed to be overriding the member function of the base class. Normally I would have noticed this, because the member function in the derived class actually called the member function of the base class that it was supposed to override. However, I didn't notice it this time because I had added a default value to the new parameter. This way, all code that used the member function would still work, I thought. But exactly this expectation caused the override in the derived class to silently cease being an override, which was very annoying indeed!
I'll give you an example of the situation I ran into. Say you've got the following code:
class A
{
public:
virtual void foo()
{
std::cout << "A::foo" << endl;
}
};
class B : public A
{
public:
virtual void foo()
{ std::cout << "B::foo" << endl;
}
};
int main()
{
A* a = new B; a->foo();
}
You get into a situation where foo
needs an extra parameter, and you change it like this:
class A
{
public:
virtual void foo(int n = 3)
{
std::cout << "A::foo(" << n << ")" << endl;
}
};
class B : public A
{
public:
virtual void foo()
{ std::cout << "B::foo" << endl;
}
};
int main()
{
A* a = new B; a->foo();
}
Because you're smart, you've added a default value to the new parameter, so that all the code using A
wouldn't have to be changed. Surprisingly, the program now prints "A::foo(3)
" instead of "B::foo
" as it did before. How could this happen? Well, obviously you forgot to change B
as well, and unfortunately the compiler didn't give you a warning about this. And how could it? It doesn't know that B
's foo
is supposed to override A
's foo
!
After thinking about what happened for a while, I decided to do something about it. I wrote a macro that I could use in derived classes, which would make absolutely sure that overriding member functions were actually overriding a base class member with the exact same signature. I'm making it available for the community so that they don't have to fall into the same trap that I fell into.
Using the code
Using this macro is pretty easy. Let's say you have a class A
, and a class B
derived from it. Say class A
has a member function A::foo
that you want to override in class B
. Normally, you would write:
class B
{
};
However, when you want to do override checking, you write:
#include "override.h"
class B
{
OVERRIDE(A,void,foo,(int,double));
};
Now, when the signature of foo
in A
changes so that A::foo(int,double)
does not exist anymore, you will get an error stating this. Also, if B
suddenly isn't derived from A
anymore, you will get an error.
Let's put this into practice in the example that I gave in the introduction. If you would have used my OVERRIDE
macro, you would have written your original code like this:
class A
{
public:
virtual void foo()
{
std::cout << "A::foo" << endl;
}
};
class B : public A
{
public:
OVERRIDE(A,void,foo,())
{
std::cout << "B::foo" << endl;
}
};
int main()
{
A* a = new B; a->foo();
}
Now let's see what would have happened if you'd made the same change that was made in the introduction, that is, if you added a parameter to A
's foo
function. You'd then get this code:
class A
{
public:
virtual void foo(int n = 3)
{
std::cout << "A::foo(" << n << ")" << endl;
}
};
class B : public A
{
public: OVERRIDE(A,void,foo,())
{
std::cout << "B::foo" << endl;
}
};
int main()
{
A* a = new B; a->foo();
}
If you hadn't used OVERRIDE
, this would have compiled just fine, and you would only have noticed your mistake after running the program. Now however, due to the OVERRIDE
macro, your compiler generates an error, alerting you to your mistake!
For completeness, I'll give you a list of the parameters of the macro:
- The base class whose member function we want to override
- The return type of the member function
- The name of the member function that we're overriding
- The list of parameters of the member function, in parentheses!
It is important to know that the checking does generate some code, and even though the code is never called, you might want to disable the checks when you're doing a release build. You can do this by defining WITHOUT_OVERRIDE_CHECKING
in your project settings.
Caveats
The trickery that this macro pulls does not work on all compilers, unfortunately. The reason for this is that some compilers (the Comeau compiler, for instance) do not allow you to take the address of a member function that is protected or private. On these compilers, the checks will work fine for overriding public member functions, but they will not work for protected or private member functions. To get around this, you must define WITHOUT_OVERRIDE_CHECKING
when using these compilers, so that the checks are not compiled.
History
- Version 0.1, 24 October 2003: Initial release.
- Version 0.2, 16 November 2003: Major overhaul, moved macro usage to class definition, added support for protected functions.