#include <iostream>
using namespace std;
class C
{
public:
virtual void a();
};
class D : public C
{
public:
void a() { cout<<"D::a\n"; }
void b() { cout<<"D::b\n"; }
};
int main()
{
D a;
a.b();
return 0;
}
我收到关于undefined reference to 'vtable for C'
的链接错误。这是什么意思,为什么呢?
我知道问题显然是基类有一个永远不会定义的非纯虚函数,但是如果我从不调用它,为什么这会打扰链接器呢?为什么它与我声明和未定义的任何其他功能不同,如果我从不称呼它我很好? 我我对这些细节很感兴趣。
答案 0 :(得分:9)
C ++编译器的大多数实现为每个类生成 vtable ,这是一个虚函数的函数指针表。与任何其他数据项一样,vtable只能有一个定义。一些C ++编译器在编译一个类型中第一个声明的虚函数的实现时生成此vtable(这保证了vtable只有一个定义)。如果您未能为第一个虚函数提供实现,则您的编译器不会生成vtable,并且链接器会因链接错误而抱怨缺少的vtable。
如您所见,具体细节取决于您选择的编译器和链接器的实现。并非所有工具链都是相同的。
答案 1 :(得分:4)
答:这是你问题的答案:)
问:为什么它与我声明和未定义的任何其他功能不同,如果我从不称呼它我很好?
答:因为它不仅仅是一个“功能”。这是一个虚拟类方法。
建议:
声明三个不同的类:
1)一个简单的方法
2)虚拟方法(如上面的“C”)
3)抽象虚拟方法(= 0)
生成程序集输出(例如GCC的“-S”)
比较三种情况。仔细注意是否创建了*构造函数:)
答案 2 :(得分:0)
如果您希望C
成为纯虚拟基类,则需要告诉编译器&#34;不会实现a()
&#34;,您可以通过将... a() = 0;
用于类的声明。编译器正在尝试使用基类vtable,但是没有一个!
答案 3 :(得分:0)
在某些情况下可能会出现对实际功能定义的需求。您已命名其中一种情况:如果您调用,则必须定义该函数。但是,这还不是全部。考虑这段代码
void foo();
int main() {
void (*p)() = &foo;
}
此代码从不调用foo
。但是,如果您尝试编译它,典型的编译器会抱怨在链接阶段缺少foo
的定义。因此,获取函数的地址(即使你从不调用它)恰好是必须定义函数的情况之一。
这就是虚函数的问题所在。虚函数通常通过虚拟表实现:包含指向虚函数定义的指针的表。无论您是否实际调用函数,此表都由编译器无条件地预先形成和初始化。编译器将基本上隐式地获取程序中每个非纯虚函数的地址,并将其放入相应的表中。因此,即使您从未调用过它,每个非纯虚函数也需要一个定义。
确切的虚拟机制是一个实现细节,因此您最终会遇到不同的特定错误(如缺少函数定义或缺少虚拟表本身),但这些错误的根本原因就是我上面所描述的。