【问题标题】:Building Qt app with CONFIG += staticlib causes "undefined reference to vtable" errors使用 CONFIG += staticlib 构建 Qt 应用程序会导致“未定义对 vtable 的引用”错误
【发布时间】:2014-02-26 00:53:21
【问题描述】:

编辑:我对这篇文章进行了大量编辑,以将项目精简到它的本质。我还添加了Github repository,包括本文未提及的文件。


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

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

我正在 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 文件。它表示我所有其他专业文件,因为静态链接描述(由 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,没错,但它似乎不起作用。我开始怀疑构建过程中存在错误。
  • 显示继承自Engine的代码
  • @paulm,您的暗示建议至少部分正确。 :) 从 Engine 继承的代码有一个额外的 Q_OBJECT 宏(不必要,因为继承的类已经有了)。奇怪的是,构建过程从未标记继承类中的错误。我仍在试图弄清楚我尝试过的 15 件事中究竟哪一个有所帮助,但我会尽快发布解决方案。
  • 有时 msvc 或 clang 会给出更有意义的错误 - 虽然在这种情况下可能不会,但可能值得一试

标签: c++ linux qt gcc qt-creator


【解决方案1】:

答案非常简单:这些库的链接顺序错误。

我查看了调用链接器的命令(就在链接器错误上方):

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

我还查看了代码:Football 子项目是指 Stadium 子项目,因此库的顺序错误,请参阅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 机器上编译和运行。

【讨论】:

  • 你在开玩笑吧。我不敢相信 gcc 链接器如此简单。我用 Java 编程太久了。我更改了原始项目中的顺序,它起作用了。可悲的是,我故意按该顺序添加库,因为“将非依赖库放在首位是有意义的”。嘘。谢谢你的帮助。我今天学到了一些新东西。
  • @jmbeck 我很高兴它有帮助! :) 我猜链接器很简单,因为它使链接器的实现更简单,链接过程更快。对于非常大的项目,链接时间可能很重要,例如从源代码构建 clang。
【解决方案2】:

好的,我想出了解决方案。我遇到了三个不同的问题,这些问题在更改时会清除 vtable 错误。不幸的是,我不知道为什么后两个更改是必要的。

1。派生类中的 Q_OBJECT

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

2。引擎构造函数

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

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

要求构造函数和析构函数都定义在纯抽象类中。我不理解为什么。我在标题中添加了这些行,在类定义之外:

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

这仍然让我很困扰。为什么这些改变是必要的?为什么这发生在 gcc/Linux 上?这肯定是一个 Qt 错误,对吧?

【讨论】:

  • @Ali,我已经完全重写了我最初的帖子,并将项目简化为最少的文件集。您可以从 Github 下载文件(请参阅原始帖子)。在这一点上,我很想知道为什么我必须将构造函数移动到标题。
【解决方案3】:

你可以看看这个其他问题:

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

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

【讨论】:

    【解决方案4】:

    我看不到你在哪里运行 moc 编译器。 moc 创建一个文件,为您的 QObject 派生类解析其他内容。

    如果 moc 正在运行,则您的命名空间可能是问题所在。不知道 moc 不能很好地与命名空间一起工作。我喜欢命名空间,但 Qt 在人们开始到处使用它们之前就已经存在了。

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

    另一种可能性是您的 makefile 已过期。在这种情况下,您需要强制运行 qmake 以确保它们被正确刷新。

    【讨论】:

    • 在 Qt Creator 构建项目时,moc 编译器似乎会自动运行。 .moc 文件出现在构建目录中。命名空间问题可能是一个问题,但它在 Windows 中构建得很好。那么它是 GCC/MSVC 问题,还是 Qt 构建过程问题?也许 Qt 没有在 Linux 中为 gcc 提供正确的路径?我什至无法猜测如何解决这个问题。 Q_OBJECT 是必需的,如前所述,我已经多次使用 qmake(清理、删除构建目录等)进行了重建。
    • moc 由 qmake 生成的 makefile 运行。 .pro 文件是 qmake 的输入。你的答案几乎在所有方面都是错误的。
    • 如果类不用作另一个对象的子对象或父对象,则不需要 QObject。让 QObject 成为另一个对象的子对象的“好处”是删除是自动的。要知道 moc 是否正常运行,您应该在您的 qmake 文件中的每个头文件的构建目录中看到一个 .cxx 文件。
    【解决方案5】:

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

    【讨论】:

    • 内联析构函数是来自一些认为它可能会有所帮助的人的建议。删除它不会影响错误。
    • (编辑时间用完):不幸的是,将析构函数删除到 CPP 文件(或完全删除)不会改变任何内容。我也试过让它不纯,但没有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-30
    • 1970-01-01
    • 2021-11-25
    • 2018-12-16
    • 1970-01-01
    • 2011-08-16
    相关资源
    最近更新 更多