使用CONFIG + = staticlib构buildQt应用程序会导致“未定义引用vtable”错误

编辑:我已经大量编辑这篇文章剥夺项目到其要点。 我还添加了一个Github仓库 ,包括这篇文章中没有引用的文件。


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

  1. 体育场 – configuration为TEMPLATE = libCONFIG += staticlib
  2. 足球 – configuration为TEMPLATE = libCONFIG += staticlib并使用Field库的库。
  3. 服务器 – 使用体育场和足球库的QML应用程序。

我正在Windows 8.1(MSVC2012)和Linux(gcc 4.8.1)上构build这个应用程序。 它在Windows上没有问题 ,但Linux构build行为奇怪。

我得到的错误是这样的:

 undefined reference to 'vtable for Stadium::Engine' 

我已经将这个项目改写为一组显示错误的裸文件。 你可以在Github上find它: 足球 。 随意克隆它,看看你自己的所有错误。 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 

这是从上面的Stadium Engine.h文件inheritance的Football 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移动到头文件,那么它的构build没有错误。

下面是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,删除构build目录,并重build。 在Linux中build立这个项目的唯一方法是去掉Stadium库的.pro文件中的CONFIG += staticlib行(和相应的else:unix: PRE_TARGETDEPS += $$OUT_PWD/../stadium/libstadium.a当然也是Game.pro中的一行)。 这成功地build立了项目并且运行没有问题。 但我不明白为什么。 我也不明白为什么它定义的构造函数定义的重要。

有任何想法吗?

答案是令人失望的简单:图书馆的链接顺序错误。

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

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

我还查看了代码:Football子项目引用了Stadium子项目,因此库的顺序不正确,请参阅GCC C ++链接程序错误的接受答案:未定义的引用“XXX的vtable”,未定义的引用“ ClassName :: ClassName()'的解释。

实际上,如果我交换server.pro文件中的两个库(从commit 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机器上编译和运行得很好。

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

你可以看看其他的问题:

我尝试编译你的STADIUM_ENGINE代码作为一个静态库,然后从一个应用程序链接,我刚刚得到错误,当虚拟的纯粹的析构函数被定义(如预期)。 如果你没有定义的虚拟析构函数,你将不能实例化任何派生类。

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

好的,我想出了解决办法。 我有三个不同的问题,当改变,清除vtable错误。 不幸的是,我不知道为什么第二次改变是必要的。

1.派生类中的Q_OBJECT

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

2.引擎构造函数

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

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

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

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

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

我没有看到你在哪里运行moc编译器。 moc创建一个文件,为您的QObject派生类解析额外的东西。

如果moc正在运行,您的命名空间可能是问题。 不知道moc与命名空间是否合作。 我喜欢命名空间,但是在人们开始使用它们之前,Qt已经存在了。

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

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