C++未定义对Vtable的引用

22

我正在学习C++。我试图完成一个练习,其中我定义了一些实现单个函数的纯虚拟类的实现。我在链接使用这些实现的类时遇到了问题。

==> BasicMath.h <==
#ifndef BASIC_MATH_H
#define BASIC_MATH_H

#include<string>
#include<vector>    

class BasicMath { };


#endif // BASIC_MATH_H

==> Operation.h <==

#ifndef OPERATION
#define OPERATION

#include<string>
#include<vector>    

class Operation {
 public:
  virtual void perform(std::vector<std::string> vec) = 0;
};


#endif // OPERATION

==> Sum.h <==
#ifndef SUM_H
#define SUM_H

#include "Operation.h"

class Sum: public Operation {
 public:
  void perform(std::vector<std::string> vec);
};

#endif // SUM_H

==> BasicMath.cpp <==
#ifndef BASIC_MATH_C
#define BASIC_MATH_C

#include <string>
#include <vector>
#include <iostream>
#include "BasicMath.h"
#include "Sum.h"

int main(int argc, char* argv[]) {
  Sum op;
}

#endif // BASIC_MATH_C

==> Sum.cpp <==
#ifndef SUM_C
#define SUM_C

#include <vector>
#include <string>
#include <iostream>
#include "Sum.h"

void Sum::perform(std::vector<std::string> vec) {
    using namespace std;
    int total = 0;
    cout << "Total: " << total << "\n";
};

#endif // SUM_C

编译:

$ g++ -c Sum.cpp
$ g++ -o BasicMath BasicMath.cpp
/tmp/cc1VXjNl.o:BasicMath.cpp:(.text$_ZN3SumC1Ev[Sum::Sum()]+0x16): undefined reference to `vtable for Sum'
collect2: ld returned 1 exit status

我95%确定我至少在这里做了一件愚蠢的事情——但我的大脑拒绝告诉我是什么。

我看过这个问题,但没有成功解决我的问题。


1
附注:您绝对希望通过引用而不是值传递std::vector。 - EboMike
附注2:带有虚函数的类应该有虚析构函数。 - EboMike
我已经添加了g++标签,因为你的问题在于如何让编译器(更准确地说是链接器)链接目标文件,这取决于编译器。 - David Rodríguez - dribeas
7个回答

31

我刚遇到了同样的问题,但我的问题是我在.cpp文件中没有编写析构函数代码。

class.h:

class MyClass {
public:
    MyClass();
    virtual ~MyClass();
};

class.cpp:

MyClass::MyClass() {}

我刚刚收到一个vtable错误信息,实现(空的)析构函数解决了问题。

[编辑] 因此,更正后的类文件如下所示:

MyClass::MyClass() {}
MyClass::~MyClass() {}

4
显然,这种情况可能很普遍。+1...这帮助我解决了同样的问题。 - IAbstract
第二个代码块并不是你所说的“实现(空)析构函数”,而是实现构造函数。 - DBedrenko
1
第二个代码块有意显示了导致错误的状态。我认为之后的句子已经足够解决问题,但我添加了“更正”的内容。 - Till Kolditz

20

你在编译和链接的命令行中没有包含Sum.o目标文件(第二个g++使用)。


好的,那真是太容易了。谢谢Noah。关于传递引用等其他要点,我会接下来处理的,现在我已经解决了这个错误。g++手册加入阅读列表! - Ben Fitzgerald
正确,我没有编译所包含的类的 cpp 文件。 - zpon

10

如果您忘记纯虚函数的= 0,也会发生该错误。

错误:

class Base {
    public:
        virtual void f();
};

class Derived : public Base {
    public:
        virtual void f() {}
};

int main() {
    Derived d;
    Base *b = &d;
    (void)b;
}

没有错误:

class Base {
    public:
        virtual void f() = 0;
};

这是因为没有 = 0,C++ 不知道它是一个纯虚函数,将其视为声明,期望稍后定义。
g++ 5.2.1 上测试通过。
截至 GCC 11.2.0 的测试结果,错误消息已更改为:
undefined reference to `typeinfo for Base'

命令:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp

1
伙计,你救了我的命 XD - vivi

6

已经有几个人指出了你遇到的问题的解决方案。

我会补充一些不同的东西。你只需要在头文件中使用头部守卫。你在源文件中也包含了它们,但实际上这并没有意义。例如,在sum.cpp中,我已经注释掉了你真正不需要(甚至不想要)的那些行:

//#ifndef SUM_C
//#define SUM_C
//
#include <vector>
#include <string>
#include <iostream>
#include "Sum.h"

void Sum::perform(std::vector<std::string> vec) {
    using namespace std;
    int total = 0;
    cout << "Total: " << total << "\n";
};

//#endif // SUM_C

仅供参考,与其使用perform,我会使用operator()

class Operation {
 public:
  virtual void operator()(std::vector<std::string> vec) = 0;
};

这显然也是你需要为 Sum 进行重载的内容。使用它时,不要像这样:

Sum op;
op.perform();

您可以像这样使用:

您可以使用类似以下的代码:

Sum op;
op();

当您将自己的类与其他调用函数操作的类(例如标准库中的类)结合使用时,这将特别方便,无论它们是否真正是函数或“函数对象”(像这样重载 operator() 的类,因此在语法上它们几乎可以像函数一样使用)。


嗨,杰瑞 - 在查看了一些ACE代码之后,我在我的源代码中添加了守卫,这是该网站上的一种风格http://www.cs.wustl.edu/~schmidt/ACE.html。也许对于某些框架来说这是必需的,或者现在已经过时了? - Ben Fitzgerald

2

我通常在一个纯虚类的函数末尾意外地忘记添加=0时遇到这个错误。


那与此无关。=0 意味着你的类将是抽象的。我猜问题在于你忘记写函数体了。 - EboMike
谢谢,这个答案帮了我。 - Alcamtar
当使用 ... = 0 时,不需要函数体。因此,它可以解决一些问题(尽管不能用于析构函数)。 - Alexis Wilke
为什么这样可以工作:= 0 确保编译器/链接器不会尝试查找该虚拟类的成员函数定义(实现)- 当您希望该成员函数是虚拟函数时,该虚拟类并不存在。;) - Till Kolditz

2

我遇到了和你一样的问题,我通过在CMakeLists.txt中添加三行代码来解决这个问题,具体如下:

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

嗨,请记住,正如cmake手册所示,这些非常特定于cmake和QT项目;-) - Till Kolditz
谢谢,我曾因不知道如何使用MOC而苦恼。由于某些原因,我的头文件没有被CMAKE_AUTOMOC正确识别,所以我不得不使用 #include "moc_<header_base>.cpp" 而不是 #include "<header_base>.h"。希望这能帮助遇到同样问题的人。 - Imesh Chamara

1

你只是编译了 BasicMath.cpp 而没有编译 Sum.cpp - 你的链接器不知道 Sum.cpp 的存在。你需要一起编译它们,即用 Sum.cpp BasicMath.cpp 一次性编译,或者你可以独立编译 .cpp 文件,然后通过调用 g++ 来使用两个 .o 文件创建可执行文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接