更改头文件的位置会导致在使用CMake编译时丢失vtable错误

时间:2019-06-03 23:22:33

标签: c++ qt cmake

对于一个大型C ++项目,我需要从qmake过渡到CMake,但是在研究玩具示例时,遇到了一些我不理解的行为。该示例代码具有一个头文件,当该头文件移入子目录时,MainWindow类会丢失vtable错误。

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
project(HelloCMake)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt5Widgets CONFIG REQUIRED)

include_directories("include")
set(INCLUDES include/mainwindow.h)
set(SOURCES
    main.cpp
    mainwindow.cpp
    mainwindow.ui
)

add_executable(hello-cmake ${SOURCES})   # error
# add_executable(hello-cmake ${SOURCES} ${INCLUDES})   # no error
target_link_libraries(hello-cmake Qt5::Widgets)

include / mainwindow.h (样板)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

mainwindow.cpp (样板)

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow() {
    delete ui;
}

main.cpp (样板)

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

这是我运行make时(第一次运行cmake .之后看到的内容)

[ 20%] Automatic MOC and UIC for target hello-cmake
[ 20%] Built target hello-cmake_autogen
[ 40%] Linking CXX executable hello-cmake
Undefined symbols for architecture x86_64:
  "vtable for MainWindow", referenced from:
      MainWindow::MainWindow(QWidget*) in mainwindow.cpp.o
      MainWindow::~MainWindow() in mainwindow.cpp.o
  NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [hello-cmake] Error 1
make[1]: *** [CMakeFiles/hello-cmake.dir/all] Error 2
make: *** [all] Error 2

如果通过将第二个add_executable命令交换为CMakeLists.txt中的第一个命令来将标头添加到目标,则错误消失了。我也可以通过将标头与.cpp文件一起移动到基本目录中来使错误消失。但是,我想知道这里到底发生了什么。我了解将标头文件包含在目标中的一般价值,但是如果不执行此操作,为什么会生成丢失的vtable错误?除非我有严重的误解,否则无论mainwindow.h的头是否为mainwindow.cpp的一部分,#include的所有内容都应在借助add_executable进行编译时可用。声明。

- 编辑

这里是ui_mainwindow.h的内容,以防它们之间存在某种关联。

/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.10.1
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H

#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_MainWindow
{
public:
    QWidget *centralWidget;
    QPushButton *pushButton;
    QMenuBar *menuBar;
    QToolBar *mainToolBar;
    QStatusBar *statusBar;

    void setupUi(QMainWindow *MainWindow)
    {
        if (MainWindow->objectName().isEmpty())
            MainWindow->setObjectName(QStringLiteral("MainWindow"));
        MainWindow->resize(393, 307);
        centralWidget = new QWidget(MainWindow);
        centralWidget->setObjectName(QStringLiteral("centralWidget"));
        pushButton = new QPushButton(centralWidget);
        pushButton->setObjectName(QStringLiteral("pushButton"));
        pushButton->setGeometry(QRect(10, 10, 113, 32));
        MainWindow->setCentralWidget(centralWidget);
        menuBar = new QMenuBar(MainWindow);
        menuBar->setObjectName(QStringLiteral("menuBar"));
        menuBar->setGeometry(QRect(0, 0, 393, 22));
        MainWindow->setMenuBar(menuBar);
        mainToolBar = new QToolBar(MainWindow);
        mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
        MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar);
        statusBar = new QStatusBar(MainWindow);
        statusBar->setObjectName(QStringLiteral("statusBar"));
        MainWindow->setStatusBar(statusBar);

        retranslateUi(MainWindow);
        QObject::connect(pushButton, SIGNAL(released()), pushButton, SLOT(hide()));

        QMetaObject::connectSlotsByName(MainWindow);
    } // setupUi

    void retranslateUi(QMainWindow *MainWindow)
    {
        MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr));
        pushButton->setText(QApplication::translate("MainWindow", "Do not press", nullptr));
    } // retranslateUi

};

namespace Ui {
    class MainWindow: public Ui_MainWindow {};
} // namespace Ui

QT_END_NAMESPACE

#endif // UI_MAINWINDOW_H

2 个答案:

答案 0 :(得分:1)

元对象编译器(moc)从标头生成moc_*.cpp文件。因此,构建系统需要知道要提供哪些头。与需要知道要处理哪些文件的C ++编译器相似。因此,在使用CMake和Qt时,您需要将头文件视为源文件。 CMake非常聪明,不会要求C ++编译器对它们进行编译,因此将它们添加到其他库中也没有什么害处。

不幸的是,Qt-CMakeCMake-Qt文档都没有明确说明这一点,但是您应该始终add_executable / add_library中将标头添加到源列表中CMAKE_AUTOMOC工作。


另一个重要的事情是最低要求的CMake版本。 CMake 2.6不支持Qt5。 CMake 2.8也没有。 official Qt documentation声称最低CMake版本为3.1.0。但是CMake 3.0 already has Qt5 support。那些版本仍然太旧,缓慢且有错误,因此请考虑更新需求。我建议使用3.11或最新的稳定版。

答案 1 :(得分:0)

我能够将我的实际项目迁移到CMake上:只是一个小小的麻烦:链接器对QObject派生类中的任何信号函数都不满意。 (对于不熟悉Qt的人来说,元对象编译器moc神奇地将标为头文件中信号的某些空函数转换为C ++编译器可以使用的实函数。)我的结论是,这两个问题都是由CMake看不到引起的QObject派生类的头文件,因此尽管进行了CMAKE_AUTOMOC设置,它们仍未发送到moc。

结果是,如果moc(或uic或rcc)需要编译文件,则CMake必须在建立任何依赖目标之前知道它的存在。我为项目使用的粗略解决方案是将所有头文件都放在目录中的grep Q_OBJECT中,然后将此列表复制/粘贴到CMakeLists.txt中的长set命令中

set(MOC_SOURCES
    include/applewidget.h
    include/borangewidget.h
    ...
)

,然后将${MOC_SOURCES}添加到我的add_executable行中。可能有一个更复杂的解决方案,其中涉及分别构建这些对象,但是我对CMake的使用尚未达到这种复杂程度。