I've come across quite weird scenario today. When directly calling a pure virtual method in Interface constructor, I get a undefined reference error.
class Interface
{
public:
virtual void fun() const = 0;
Interface(){ fun(); }
};
class A : public Interface
{
public:
void fun() const override {};
};
int main()
{
A a;
}
Results in:
prog.cc: In constructor 'Interface::Interface()':
prog.cc:5:22: warning: pure virtual 'virtual void Interface::fun() const' called from constructor
5 | Interface(){ fun(); }
| ^
/tmp/ccWMVIWG.o: In function `main':
prog.cc:(.text.startup+0x13): undefined reference to `Interface::fun() const'
collect2: error: ld returned 1 exit status
However, wrapping a call to fun() in a different method like this:
class Interface
{
public:
virtual void fun() const = 0;
Interface(){ callfun(); }
virtual void callfun()
{
fun();
}
};
class A : public Interface
{
public:
void fun() const override {};
};
int main()
{
A a;
}
Compiles just fine and (obviously) crashes with pure virtual call error. I've tested it on latest GCC 8.2.0 and 9.0.0 and Clang 8.0.0. Out of those, only GCC produces a linker error in the first case.
Wandbox links for a full working example with the error:
EDIT: I'm getting flagged for duplication, but I'm not sure how this question is duplicated. It doesn't have anything to do with dangers of calling pure virtual method (from constructor or whatnot), I'm aware of them.
I was trying to understand why the compiler permits this call in one scenario, and fails to do so in another, which was explained very well by Adam Nevraumont.
EDIT2:
It seems, that even if callFun
is not virtual, it still somehow prevents GCC from devirtualizing and inlining fun
call. See the example below:
class Interface
{
public:
virtual void fun() const = 0;
Interface(){ callfun(); }
void callfun()
{
fun();
}
};
class A : public Interface
{
public:
void fun() const override {};
};
int main()
{
A a;
}
You aren't calling the pure virtual function, you are doing a lookup in the vtable for the current entry in the virtual function table for that function.
As it happens, at that point it is a pure virtual function, so you crash due to UB.
In the first case, you are getting a linker error because gcc is devirtualizing the call to fun
in the ctor. A devirtualized call to fun
directly calls the pure virtual method. This is possible because while constructing Interface
, the compiler knows the state of the virtual function table (derived class modifications to it do not occur yet).
In the second case, the compiler can devirtualize the call to callFun
from the ctor. But the call to fun
from within callFun
cannot be devirtualized, as callFun
could be called from outside the ctor in another method. Devirtualizing it would be incorrect in the general case.
In this specific case, if the compiler devirtualized callFun
and then inlined it, it could then devirtualize fun
in the inlined copy. But the compiler doesn't do this, so no devirtualization occurs.
As an aside, you can implement that pure virtual function and cause every example you provided to both link and run fine.
void Interface::fun() const {}
anywhere in any .cpp
file linked in will make your code link, and be correct regardless. Pure virtual doesn't mean "has no implementation" in C++, it just means "derived class must provide an override, and it is legal for me not to have an implementation".