为什么在没有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的情况下调用纯虚方法不会导致链接器错误?的主要内容,如果未能解决你的问题,请参考以下文章
CoreBluetooth - 在没有 LightBlue 的情况下不会调用后台模式下的 didDiscoverPeripheral