头文件可见,不等于已经生成元对象代码
跳转到“头文件可见,不等于已经生成元对象代码”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.txtinclude/gui/BalanceMeasureScreen.hsrc/gui/BalanceMeasureScreen.cppsrc/main.cppinclude/gui/BalanceMeasureScreen.h:
#ifndef BALANCE_MEASURE_SCREEN_H#define BALANCE_MEASURE_SCREEN_H#include <QWidget>
class BalanceMeasureScreen final : public QWidget { Q_OBJECTpublic: explicit BalanceMeasureScreen(QWidget *parent = nullptr); void measure(int value);signals: void measured(int value);};#endifsrc/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 文档