我今天遇到了很奇怪的情况。在接口构造函数中直接调用纯虚拟方法时,出现未定义的引用错误。
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;
}
答案 0 :(得分:5)
您不是在调用纯虚函数,而是在vtable中对该函数的虚函数表中的当前条目进行查找。
碰巧的是,这只是一个纯虚函数,因此您由于UB而崩溃。
在第一种情况下,您会收到链接器错误,因为gcc正在取消对ctor中对fun
的调用的虚拟化。对fun
的非虚拟化调用直接调用纯虚拟方法。之所以可能这样做,是因为在构造Interface
时,编译器知道虚函数表的状态(对它的派生类修改尚未发生)。
在第二种情况下,编译器可以将ctor对callFun
的调用虚拟化。但是无法撤消从fun
内部对callFun
的调用,因为可以使用另一种方法从ctor外部调用callFun
。将其取消虚拟化在一般情况下是不正确的。
在这种特定情况下,如果编译器对callFun
进行虚拟化,然后对其进行内联,则可以在内联副本中对fun
进行虚拟化。但是编译器不会这样做,因此不会发生非虚拟化。
顺便说一句,您可以实现该纯虚函数,并使您提供的每个示例都链接并正常运行。
void Interface::fun() const {}
在链接的任何.cpp
文件中的任何位置都将使您的代码链接,并且无论如何都是正确的。纯虚拟语言在C ++中并不意味着“没有实现”,而只是意味着“派生类必须提供重写,并且对我来说,没有实现是合法的。”