为什么调用没有主体的纯虚方法不会导致链接器错误?
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".
我今天遇到了一个很奇怪的场景。在接口构造函数中直接调用纯虚方法时,出现未定义引用错误。
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".