Search code examples
c++inheritancepreprocessorvirtual-functions

Using #ifdef preprocessor around virtual functions causes runtime error in program linked against libraries


I was trying to use #ifdef preprocessor around virtual functions. A simplified version of code looks like following:

class Base
{
#ifdef ENABLE_FLAG
    virtual void function1();
#endif //ENABLE_FLAG

    virtual void function2();
    virtual void function3();
};

class Child : public Base
{
#ifdef  ENABLE_FLAG
    void function1() override;
#endif //ENABLE_FLAG

    void function2() override;
    void function3() override;
};

The code compiles fine. However, when my application is calling Child::function3(), it actually ended up calling Child::function2() for some reason. I think the preprocessor has messed up the virtual table some how.

I am running debug mode in visual studio 2017. I'm curious what is the cause of this runtime problem. Is this a compiler dependent behaviour?

Another interesting thing to note is that if I make sure ENABLE_FLAG is defined and remove the #ifdef clause in the Child class and keep the one in Base class, the compiler is actually throwing a compilation error. What difference does it make here?

UPDATE: This class is used both in a main program and in a library.


Solution

  • Virtual functions are listed in a per-class table - the virtual function table or vtable - each instance of the class contains a pointer to this table which itself contains pointers to each of the virtual functions for that class, in the order in which they are listed in the class declaration (*). (There are plenty of tutorial articles and videos that describe virtual tables if you want to look.)

    So if you compile part of your program with that #ifdef enabled and another part of your program with that #ifdef not enabled - there will be a different number of virtual functions that the different parts see, and the vtables will be different. Which is not supposed to happen, and leads to the problem you're seeing.

    So don't do that. You're violating a very strict rule of C++ called ODR or "One Definition Rule". Everything is up in the air if you do that - i.e., anything and everything can go wrong.

    (Odd thing about the ODR: for such a very strict crucial rule, the compiler (i.e., compilation system as a whole) is not required in any way to tell you you've violated it. So, really, don't.

    By the way, all libraries and the main program must see the same class declaration! That stuff above about the ODR? The C++ standard doesn't know about "libraries" - static, dynamic, or otherwise. For it there are only programs and compilation units. (These "library" things are just a convenience the compilation systems provide for us.) So all the rules apply across entire programs - and the ODR is the one that will most often bite you here! (As you have just discovered...)

    (*) To a first approximation, not including multiple inheritance ...