为什么调用没有主体的纯虚方法不会导致链接器错误?

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

我今天遇到了一个很奇怪的场景。在接口构造函数中直接调用纯虚方法时,出现未定义引用错误。

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 对此进行了很好的解释。

编辑2: 看起来,即使 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 中查找该函数的虚函数 table 中的当前条目。

碰巧,此时它是一个纯虚函数,所以你因UB而崩溃。

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

在第二种情况下,编译器可以将 ctor 对 callFun 的调用去虚拟化。但是从 callFun 内部对 fun 的调用不能去虚拟化,因为 callFun 可以从 ctor 外部以另一种方法调用。在一般情况下去虚拟化它是不正确的

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

顺便说一句,您可以实现纯虚函数,并使您提供给 link 和 运行 的每个示例都很好。

void Interface::fun() const {}

任何 .cpp 文件中的任何地方 link 都会使您的代码 link 正确无误。纯虚拟在C++中并不意味着"has no implementation",它只是意味着"derived class must provide an override, and it is legal for me not to have an implementation".