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

Mar*_*Bąk 2 c++ constructor virtual-functions pure-virtual undefined-behavior

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

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

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

int main()
{
    A a;
}
Run Code Online (Sandbox Code Playgroud)

结果是:

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
Run Code Online (Sandbox Code Playgroud)

但是,使用不同的方法将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;
}
Run Code Online (Sandbox Code Playgroud)

用纯虚拟调用错误编译就好了(显然)崩溃.我在最新的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;
}
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 5

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

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

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

在第二种情况下,编译器可以将callFun来自ctor 的调用虚拟化.但是fun来自内部的呼叫callFun不能被虚拟化,因为callFun可以在另一种方法中从ctor外部调用.在一般情况下,将其虚拟化是不正确.

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

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

void Interface::fun() const {}
Run Code Online (Sandbox Code Playgroud)

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

  • @MarcinBąk是的,因为除非内联,否则你无法虚拟化.您可以从ctor中虚拟化呼叫,因为您知道ctor中vtable的确切状态; 在ctor中调用的函数可以从其他地方调用,因此,虚拟化要求将它们首先内联到ctor中(即使这样,优化器也可能在内联传递后不执行虚拟化传递) (2认同)