使用CONFIG += staticlib构建Qt应用程序会导致“undefined reference to vtable”错误

17

编辑:我对此帖子进行了大量编辑,将项目简化到其基本要素。我还添加了一个Github代码库,其中包括此帖子中未引用的文件。


我有一个使用subdirs模板的 Qt Creator 项目(qmake,Qt 5.2.0,Creator 3.0.0)。它有三个子项目:

  1. Stadium-配置为TEMPLATE=libCONFIG+=staticlib的库。
  2. Football - 配置为TEMPLATE = libCONFIG += staticlib的库,并使用Field 库。
  3. Server - 一个 QML 应用程序,使用 Stadium 和 Football 库。

我在 Windows 8.1(MSVC2012)和 Linux (gcc 4.8.1) 上构建这个应用程序。在 Windows 上可以正常工作,但是在 Linux 上构建时表现出奇怪的行为。

我得到的错误看起来像这样:

undefined reference to 'vtable for Stadium::Engine'

我已经将这个项目重写成一组裸文件,显示了错误。你可以在Github上找到它:Football。请随意克隆并自行查看所有的错误。提交661441c解决了问题,而提交09836f9包含了错误。

Stadium Engine.h文件是一个抽象类,长这样:

#ifndef STADIUM_ENGINE_H
#define STADIUM_ENGINE_H

#include <QObject>

namespace Stadium {

class Engine : public QObject
{
    Q_OBJECT

public slots:
    virtual void executeCommand() = 0;

};

} // namespace Stadium

#endif // STADIUM_ENGINE_H

这里是Football Engine.h文件,它继承了上面的Stadium Engine.h文件:

#ifndef FOOTBALL_ENGINE_H
#define FOOTBALL_ENGINE_H

#include <QObject>
#include "../Stadium/Engine.h"

namespace Football
{

class Engine : public Stadium::Engine
{
    Q_OBJECT

public:
    Engine();
    ~Engine() {}

public slots:
    void executeCommand();

};

} // namespace Football

#endif // FOOTBALL_ENGINE_H

还有Football Engine.cpp文件:

#include "Engine.h"

#include <QDebug>

Football::Engine::Engine()
{
    qDebug() << "[Football::Engine] Created.";
}

void Football::Engine::executeCommand()
{
    qDebug() << "[Football::Engine] The command was executed.";
}

如果我将构造函数从cpp文件移动到头文件中,它可以无错误地编译。

以下是Server.pro文件。它代表了我所有其他pro文件的示例,因为Qt Creator自动生成的静态链接描述看起来都一样。

QT       += core

QT       -= gui

TARGET = Server
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp

win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../Stadium/release/ -lStadium
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../Stadium/debug/ -lStadium
else:unix: LIBS += -L$$OUT_PWD/../Stadium/ -lStadium

INCLUDEPATH += $$PWD/../Stadium
DEPENDPATH += $$PWD/../Stadium

win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/release/libStadium.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/debug/libStadium.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/release/Stadium.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/debug/Stadium.lib
else:unix: PRE_TARGETDEPS += $$OUT_PWD/../Stadium/libStadium.a

win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../Football/release/ -lFootball
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../Football/debug/ -lFootball
else:unix: LIBS += -L$$OUT_PWD/../Football/ -lFootball

INCLUDEPATH += $$PWD/../Football
DEPENDPATH += $$PWD/../Football

win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/release/libFootball.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/debug/libFootball.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/release/Football.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/debug/Football.lib
else:unix: PRE_TARGETDEPS += $$OUT_PWD/../Football/libFootball.a
我已尝试清理、重新运行 qmake、删除构建目录并重新构建。在Linux中获取此项目构建的唯一方法是在Stadium库的.pro文件中删除CONFIG += staticlib行(当然,在Game.pro中也要删除相应的else:unix: PRE_TARGETDEPS += $$OUT_PWD/../stadium/libstadium.a行)。这将成功构建该项目,并且可以无问题地运行。但我不明白为什么会这样。我也不明白为什么构造函数定义所在的位置很重要。
有什么想法吗?

当您需要再次运行qmake时,我会看到这个。 - paulm
@paulm,说得对,但似乎不起作用。我开始怀疑构建过程中存在错误。 - jmbeck
@paulm,你的暗示至少部分正确。 :) 继承自Engine的代码有一个额外的Q_OBJECT宏(不必要,因为继承类已经有了)。奇怪的是,构建过程从未标记继承类中的错误。我仍在努力弄清楚我尝试的15件事中到底哪一件有帮助,但我很快就会发布解决方案。 - jmbeck
这是个奇怪的事情;在MSVC中编译和运行都很好,但在gcc/Linux中就出问题了。 - jmbeck
vtable 这个东西暗示着缺少对象/类定义。启用 所有 警告,并仔细检查它们。这可能是编译器之间的标准不匹配?清除所有生成的文件,还要清除 ~/.ccache,然后从头开始构建。 - vonbrand
显示剩余2条评论
5个回答

15

答案令人失望地简单:库的链接顺序不正确。

我看了一下调用链接器的命令(就在链接器错误的上面):

g++ [...] -lStadium [...] -lFootball 

我也查看了代码:足球子项目引用了体育场子项目,所以库的顺序错了,可以参考GCC C++ Linker errors: Undefined reference to 'vtable for XXX', Undefined reference to 'ClassName::ClassName()'中被接受的答案进行解释。

实际上,如果我在Server.pro文件中交换这两个库(派生自提交09836f9,为简洁起见删除了不相关的win32细节):

[...]

SOURCES += main.cpp

LIBS += -L$$OUT_PWD/../Football/ -lFootball
INCLUDEPATH += $$PWD/../Football
DEPENDPATH += $$PWD/../Football
PRE_TARGETDEPS += $$OUT_PWD/../Football/libFootball.a

LIBS += -L$$OUT_PWD/../Stadium/ -lStadium
INCLUDEPATH += $$PWD/../Stadium
DEPENDPATH += $$PWD/../Stadium
PRE_TARGETDEPS += $$OUT_PWD/../Stadium/libStadium.a

现在命令行看起来像这样:

g++ [...] -lFootball [...] -lStadium

在我的Linux机器上,它可以编译并成功运行。


2
你一定在开玩笑吧。我简直不敢相信gcc链接器如此简单。我已经用Java编程太久了。我改变了原始项目中的顺序,然后它就起作用了。可悲的是,我故意按那个顺序添加库,因为“先放非依赖库这样做很有道理”。唉,谢谢你的帮助。今天我学到了新东西。 - jmbeck
1
@jmbeck 我很高兴它有所帮助! :) 我猜链接器是简单的,因为它使链接器的实现更简单,链接过程更快。例如,在构建clang源代码时,链接时间可能会非常长。 - Ali

0

好的,我找到了解决方案。我有三个不同的问题,当更改时,清除了vtable错误。不幸的是,我不知道为什么需要更改后两个问题。

1. 派生类中的Q_OBJECT

继承了上面的Stadium::Engine类的类中,有一个额外的Q_OBJECT。当我在派生类中删除第二个Q_OBJECT时,其中一个vtable错误消失了。

2. Engine构造函数

我不明白为什么,但当派生类在CPP文件中定义构造函数时,会出现vtable错误。当在头文件中定义(在类描述内部)时,它可以正常工作。构造函数中没有任何内容(DerivedEngine() {})。我无法理解为什么这很重要。

3. 定义构造函数和虚析构函数

纯抽象类中必须定义构造函数和析构函数。我不明白为什么。我在头文件中添加了这些行,在类定义之外:

inline Stadium::Engine::Engine() {}
inline Stadium::Engine::~Engine() {}

这仍然让我很烦恼。为什么这些更改是必要的?为什么这只在gcc/Linux下发生?这肯定是Qt的错误,对吧?


@Ali,我已经完全重写了我的初始帖子,并将项目简化为最少的文件集。您可以从Github下载这些文件(请参阅原始帖子)。目前,我想知道为什么我必须将构造函数移动到头文件中。 - jmbeck

0

您可以查看以下其他问题:

我尝试将您的STADIUM_ENGINE代码编译为静态库,然后从应用程序进行链接,当未定义任何虚拟纯析构函数时,我刚刚遇到了错误(如预期所述)。如果您没有定义虚拟析构函数,则无法实例化任何派生类。

无论如何,您的类继承自QObject,它已经声明并实现了一个非纯虚析构函数。纯虚析构函数有用吗?


0

您已经将虚析构函数内联。
这有时可能会导致问题。
尝试在.cpp文件中实现析构函数。我还会从析构函数的声明中删除= 0


内联析构函数是某人建议的,他认为这可能有所帮助。但删除它不会影响错误。 - jmbeck
不幸的是,将析构函数移动到CPP文件中(或完全删除它)并没有改变任何东西。我也尝试过将其设置为非纯虚函数,但也没有帮助。 - jmbeck

-1

我没有看到你运行 moc 编译器的地方。moc 会为你派生自 QObject 的类创建一个文件,用于解决其他问题。

如果 moc 正在运行,那么可能是因为你的命名空间有问题。moc 与命名空间的兼容性并不好。我喜欢命名空间,但 Qt 在人们普遍使用命名空间之前已经存在了。

如果不是绝对需要该类的话,移除 Q_OBJECT 和从 QObject 派生也是一种解决方案。

另一个可能性是你的 makefiles 已经过时了。在这种情况下,你需要强制运行 qmake,以确保它们被正确刷新。


当Qt Creator构建项目时,moc编译器似乎会自动运行。.moc文件出现在构建目录中。命名空间问题可能是一个问题,但在Windows中构建得非常好。那么这是GCC / MSVC问题还是Qt构建过程问题?也许Qt在Linux中没有给gcc正确的路径?我甚至无法猜测如何解决这个问题。Q_OBJECT是必需的,并且如上所述,我已经使用qmake(清除,删除构建目录等)重新构建了很多次。 - jmbeck
moc 是由 qmake 生成的 makefile 运行的。.pro 文件是 qmake 的输入。你的答案几乎每个都是错误的。 - Kuba hasn't forgotten Monica
如果一个类不作为另一个对象的子对象或父对象使用,则QObject是不必要的。将QObject作为另一个对象的子对象的好处是,删除操作是自动的。要知道moc是否正常运行,您应该在构建目录中看到每个头文件对应的.cxx文件。 - Alexis Wilke

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