跳转到内容
新建笔记

Qt CMake AUTOMOC:头文件发现与链接错误排查

头文件可见,不等于已经生成元对象代码

跳转到“头文件可见,不等于已经生成元对象代码”

include/gui/BalanceMeasureScreen.h 可以被编译器找到,仍可能缺少 moc 生成代码。target_include_directories 管理查找目录,目标源文件清单和 AUTOMOC 管理哪些文件接受扫描;这是不同的步骤。

把带 Q_OBJECT 的头文件显式列入目标,是分离 include/ 与 src/ 项目的稳妥做法。但“所有这类头文件都必须显式列出”过于绝对:CMake 还会按目标中的 C++ 源文件寻找同基名头文件及带 _p 的私有头文件。因此,有的简单目录结构即使没列头文件也能成功。AUTOMOC 扫描规则

Q_OBJECT 为类提供 Qt 元对象信息,用于信号、声明的属性和元调用等机制;它不是 C++ 编译器的 RTTI。也不是每个 QObject 派生类都因为使用普通成员函数、函数指针连接或 QObject 的动态属性就必须额外写它。

一个显式列出头文件的项目

跳转到“一个显式列出头文件的项目”

保留原来的 BalanceMeasureScreen 分层命名,缩小为能检查信号和元对象的窗口类。目录为:

CMakeLists.txt
include/gui/BalanceMeasureScreen.h
src/gui/BalanceMeasureScreen.cpp
src/main.cpp

include/gui/BalanceMeasureScreen.h:

#ifndef BALANCE_MEASURE_SCREEN_H
#define BALANCE_MEASURE_SCREEN_H
#include <QWidget>
class BalanceMeasureScreen final : public QWidget {
Q_OBJECT
public:
explicit BalanceMeasureScreen(QWidget *parent = nullptr);
void measure(int value);
signals:
void measured(int value);
};
#endif

src/gui/BalanceMeasureScreen.cpp:

#include "gui/BalanceMeasureScreen.h"
BalanceMeasureScreen::BalanceMeasureScreen(QWidget *parent) : QWidget(parent) {}
void BalanceMeasureScreen::measure(int value) { emit measured(value); }

src/main.cpp:

#include "gui/BalanceMeasureScreen.h"
#include <QApplication>
#include <QByteArray>
#include <cassert>
#include <iostream>
int main(int argc, char **argv) {
QApplication app(argc, argv);
BalanceMeasureScreen screen;
int measured = 0;
QObject::connect(&screen, &BalanceMeasureScreen::measured,
&screen, [&measured](int value) { measured = value; });
screen.measure(42);
assert(measured == 42);
assert(QByteArray(screen.metaObject()->className()) == "BalanceMeasureScreen");
assert(screen.metaObject()->indexOfSignal("measured(int)") >= 0);
std::cout << "signal and meta-object passed\n";
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(BalanceMoc LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
add_executable(balance_moc
src/main.cpp
src/gui/BalanceMeasureScreen.cpp
include/gui/BalanceMeasureScreen.h
)
target_include_directories(balance_moc PRIVATE include)
target_link_libraries(balance_moc PRIVATE Qt6::Widgets)

此处没有展示窗口,也不需要进入持续事件循环;同线程信号会直接调用接收函数。CMAKE_AUTOMOC 在创建目标之前设置,也可以在目标创建之后直接设置它的 AUTOMOC 属性。

不要把 include 目录当成递归扫描开关

跳转到“不要把 include 目录当成递归扫描开关”
设置或文件关系作用
把头文件加入 add_executable 或 target_sources明确进入 AUTOMOC 的候选扫描清单
目标源文件旁有同基名头文件可由 AUTOMOC 的对应规则发现
target_include_directories提供编译和 moc 所需的头文件查找目录,不把任意头文件递归加入目标
CMAKE_INCLUDE_CURRENT_DIR ON添加当前源目录和构建目录到 include 路径,不开启任意 include 链的递归 moc 扫描
在 .cpp 内定义带 Q_OBJECT 的类按 AUTOMOC 规则在该源文件包含对应的 文件基名.moc

CMAKE_INCLUDE_CURRENT_DIR 的定义只涉及包含路径。原记录把它描述为处理所有被 include 头文件的开关,需要纠正。

为了理解发现规则,可做三次独立构建:保持上述分离目录但漏掉目标中的头文件;在此基础上加入 CMAKE_INCLUDE_CURRENT_DIR;最后恢复显式头文件。前两项不能依靠编译器已经看见头文件就认定 moc 成功。另将头文件放到同基名 .cpp 旁边,可验证其自动发现路径。每次使用新的构建目录,避免旧生成文件掩盖问题。

链接错误包含 staticMetaObject、qt_metacall、qt_metacast 或未实现的信号时,检查 AUTOMOC 是否启用、目标是否链接正确版本 Qt、头文件是否在扫描范围。生成目录通常含目标的 _autogen 子目录;没有手工 include 的 moc 输出由 mocs_compilation.cpp 汇入编译。

undefined reference to vtable 也可能来自普通虚函数或析构函数只有声明没有定义,不能见到这句就一律归因于 moc。先核对完整缺失符号,再决定修构建设置还是补函数定义。

file(GLOB_RECURSE ...) 可以搜集文件,但显式清单更便于审阅新增类。若采用 CONFIGURE_DEPENDS,CMake 可在构建检查时重新执行 glob 并据变化重新生成;原记录“新增文件必定只能手工重新配置”的表述不完整。该机制有生成器支持和检查成本等限制,仍不能代替对目标文件集合的管理。CMake file 文档

来源:原始项目记录、Qt 元对象系统。