简体   繁体   中英

Why does calling calling a pure virtual method without body does not result in linker error?

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".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM