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)
您没有调用纯虚函数,您正在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++中"没有实现",它只是意味着"派生类必须提供覆盖,对我来说,没有实现是合法的".