2
#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'. 这是什么意思,为什么?

我知道问题显然是基类有一个从未定义过的非纯虚函数,但是如果我从不调用它,为什么这会打扰链接器呢?为什么它与我声明但未定义的任何其他函数不同,如果我从不调用它我很好?
我对细节很感兴趣。

4

4 回答 4

9

C++ 编译器的大多数实现都会为每个类生成一个vtable,它是一个虚函数的函数指针表。与任何其他数据项一样,vtable 只能有一个定义。一些 C++ 编译器在编译类型中第一个声明的虚函数的实现时会生成这个 vtable(这保证了 vtable 只有一个定义)。如果您未能为第一个虚函数提供实现,则编译器不会生成 vtable,并且链接器会抱怨缺少 vtable 并出现链接错误。

如您所见,具体细节取决于您选择的编译器和链接器的实现。并非所有工具链都是相同的。

于 2012-12-25T20:47:19.300 回答
4

问:我知道问题显然是基类有一个从未定义过的非纯虚函数

答:这就是你问题的答案:)

问:为什么它与我声明但未定义的任何其他函数不同,即如果我从不调用它就可以了?

A:因为它不仅仅是一个“功能”。这是一个虚拟类方法

建议:

  • 声明三个不同的类:

    1)简单的方法

    2)一个虚拟方法(如你上面的“C”)

    3)抽象虚方法(=0)

  • 生成汇编输出(例如 GCC 的“-S”)

  • 比较这三种情况。仔细注意是否创建了 *constructor" :)

于 2012-12-25T20:44:35.417 回答
0

如果你想C成为一个纯虚拟基类,你需要告诉编译器“不会有a()”的实现,你可以通过... a() = 0;类的声明来做到这一点。编译器试图找到一个基类 vtable,但没有一个!

于 2012-12-25T21:10:34.047 回答
0

在几种情况下可能会出现对实际函数定义的需求。您已经命名了其中一种情况:如果您调用该函数,则必须对其进行定义。然而,这还不是全部。考虑这段代码

void foo();

int main() {
  void (*p)() = &foo;
}

此代码从不调用foo. 但是,如果您尝试编译它,典型的编译器会抱怨foo在链接阶段缺少定义。因此,获取函数的地址(即使您从未调用它)恰好是必须定义函数的情况之一。

这就是虚函数的用武之地。虚函数通常是通过一个虚表实现的:一个包含指向虚函数定义的指针的表。该表由编译器无条件地预先形成和初始化,无论您是否实际调用函数。编译器本质上会隐式地获取程序中每个非纯虚函数的地址,并将其放入相应的表中。出于这个原因,每个非纯虚函数都需要一个定义,即使你从未调用它。

确切的虚拟机制是一个实现细节,因此您可能会遇到不同的特定错误(例如缺少函数定义或缺少虚拟表本身),但这些错误的根本原因是我上面描述的。

于 2012-12-25T21:37:03.003 回答