为什么在没有body的情况下调用纯虚方法不会导致链接器错误?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么在没有body的情况下调用纯虚方法不会导致链接器错误?相关的知识,希望对你有一定的参考价值。

我今天遇到了相当奇怪的情况。在Interface构造函数中直接调用纯虚方法时,我得到一个未定义的引用错误。

class Interface
{
public:    
    virtual void fun() const = 0; 
    Interface(){ fun(); }
};

class A : public Interface
{
public:
    void fun() const override {};
};

int main()
{
    A a;
}

结果是:

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

但是,使用不同的方法将fun()调用包装为:

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;
}

用纯虚拟调用错误编译就好了(显然)崩溃。我在最新的GCC 8.2.0和9.0.0以及Clang 8.0.0上进行了测试。其中,只有GCC在第一种情况下产生链接器错误。

Wandbox链接以获取包含错误的完整工作示例:

编辑:我被标记为重复,但我不确定这个问题是如何重复的。它与调用纯虚方法(来自构造函数或诸如此类)的危险没有任何关系,我知道它们。

我试图理解为什么编译器在一个场景中允许这个调用,而在另一个场景中没有这样做,Adam Nevraumont对此进行了很好的解释。

EDIT2:看来,即使callFun不是虚拟的,它仍然会以某种方式阻止GCC进行虚拟化和内联fun调用。请参阅以下示例:

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;
}
答案

您没有调用纯虚函数,您正在vtable中查找该函数的虚函数表中的当前条目。

碰巧的是,那时它是一个纯粹的虚函数,因此你会因UB而崩溃。

在第一种情况下,您收到链接器错误,因为gcc正在对ctor中的fun进行虚拟化调用。对fun的虚拟化调用直接调用纯虚方法。这是可能的,因为在构造Interface时,编译器知道虚函数表的状态(对它的派生类修改尚未发生)。

在第二种情况下,编译器可以从ctor中对callFun的调用进行虚拟化。但是fun内部对callFun的调用无法被虚拟化,因为callFun可以通过另一种方法从ctor外部调用。在一般情况下,将其虚拟化是不正确的。

在这种特定情况下,如果编译器对callFun进行了虚拟化,然后将其内联,则可以在内联副本中对fun进行虚拟化。但编译器不会这样做,因此不会发生虚拟化。

顺便说一句,您可以实现该纯虚函数,并使您提供的每个示例都链接并运行正常。

void Interface::fun() const {}

在链接的任何.cpp文件中的任何地方都将使您的代码链接,并且无论如何都是正确的。纯虚拟并不意味着在C ++中“没有实现”,它只是意味着“派生类必须提供覆盖,对我来说,没有实现是合法的”。

以上是关于为什么在没有body的情况下调用纯虚方法不会导致链接器错误?的主要内容,如果未能解决你的问题,请参考以下文章

关于C++设计模式的问题,纯虚方法调用

如何在没有任何中断的情况下运行线程

为啥纯虚析构函数需要实现

虚析构函数与纯虚函数

CoreBluetooth - 在没有 LightBlue 的情况下不会调用后台模式下的 didDiscoverPeripheral

关于C++的虚函数在父类的内部调用