Создание приложения Qt с помощью CONFIG + = staticlib вызывает ошибки undefined для ссылок на vtable "

EDIT: я сильно отредактировал этот пост, чтобы лишить проект до его сути. Я также добавил репозиторий Github, в том числе файлы, на которые не ссылаются в этом сообщении.


У меня есть проект Qt Creator (qmake, Qt 5.2.0, Creator 3.0.0), который использует шаблон subdirs. Существует три подпроекта:

  • Стадион - библиотека, настроенная как TEMPLATE = lib и CONFIG += staticlib.
  • Футбол - библиотека, которая настроена как TEMPLATE = lib и CONFIG += staticlib и использует библиотеку Field.
  • Сервер - приложение 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. Это указывает на все мои другие файлы 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 - это удалить строку CONFIG += staticlib в .pro файле библиотеки стадиона (и соответствующую строку else:unix: PRE_TARGETDEPS += $$OUT_PWD/../stadium/libstadium.a в Game.pro тоже, конечно). Это успешно создает проект и запускается без проблем. Но я просто не понимаю, почему. Я также не понимаю, почему это имеет значение, когда определено определение конструктора.

Любые идеи?

Ответы

Ответ 1

Ответ неутешительно прост: библиотеки были связаны в неправильном порядке.

Я посмотрел на команду, которая вызывала компоновщик (прямо над ошибкой компоновщика):

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

Я также просмотрел код: подпроект Football относится к подпроекту стадиона, поэтому библиотеки находятся в неправильном порядке, см., например, принятый ответ ошибки GCC С++ Linker: Undefined ссылка на "vtable для XXX" , Undefined ссылка на "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.

Ответ 2

Вы ввели виртуальный деструктор.
Иногда это может привести к проблемам. Попробуйте реализовать деструктор в файле .cpp. Я также удаляю = 0 из объявления деструктора.

Ответ 3

Вы можете взглянуть на другие вопросы:

Я попытался скомпилировать ваш код STADIUM_ENGINE как статический lib, а затем связать его с приложением, и я только что получил ошибку, когда NO виртуальный чистый деструктор определен (как и ожидалось). Если у вас нет определенного виртуального деструктора, вы не сможете создать экземпляр какого-либо производного класса.

Во всяком случае, ваш класс наследует от QObject, который уже объявляет и реализует не чистый виртуальный деструктор. Полезен ли это чистый виртуальный деструктор?

Ответ 4

Хорошо, я понял решение. У меня было три различных вопроса, которые при изменении изменили ошибки 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, верно?

Ответ 5

Я не вижу, где вы запускаете компилятор moc. moc создает файл, который разрешает дополнительные вещи для ваших производных классов QObject.

Если moc запущен, может возникнуть проблема с вашим пространством имен. moc, как известно, хорошо работает с пространствами имен. Мне нравятся пространства имен, но Qt находится там, прежде чем люди начали использовать их везде.

Удаление Q_OBJECT и вывод из QObject - это другое решение, если это не совсем необходимо для этого класса.

Другая возможность заключается в том, что ваши make файлы устарели. В этом случае вы хотите принудительно запустить qmake, чтобы убедиться, что они правильно обновлены.