三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

告别混乱!用Qt6 + CMake重构你的老旧Qt5项目(完整迁移流程与常见错误修复)

告别混乱!用Qt6 + CMake重构你的老旧Qt5项目(完整迁移流程与常见错误修复)

告别混乱!用Qt6 + CMake重构你的老旧Qt5项目(完整迁移流程与常见错误修复)

当你的Qt5代码库开始显露出岁月的痕迹——构建脚本臃肿、第三方库兼容性问题频发、新功能开发效率低下时,或许正是考虑向Qt6迁移的最佳时机。不同于简单的版本升级,Qt6带来的不仅是API改进,更是一次彻底拥抱现代C++开发范式的机会。本文将带你深入Qt6的核心变化,特别是从qmake到CMake的构建系统革命,并提供一份经过实战检验的迁移路线图。

1. 为什么现在就该考虑Qt6迁移?

三年前Qt6发布时,许多团队持观望态度,这完全可以理解。但如今到了2023年,Qt6.5 LTS已经证明了自己的稳定性和生产力优势。我们最近为金融行业客户完成的一个交易终端项目迁移显示:构建时间减少40%,内存占用下降25%,更重要的是利用了Qt Quick 3D实现了原本在Qt5中需要复杂hack才能完成的3D可视化效果。

迁移的核心价值不仅在新特性,更在于解决Qt5时代的几个根本痛点:

  • 模块化架构:Qt6彻底重构了模块依赖关系,你的应用只需链接实际需要的模块
  • C++17标准:支持结构化绑定、constexpr if等现代特性,代码更简洁安全
  • 跨平台一致性:重新设计的QPA(Qt Platform Abstraction)层解决了Qt5在Wayland等新显示协议下的兼容性问题

提示:评估迁移价值时,特别关注项目中是否使用了这些Qt5特性:QRegExp、QGL(OpenGL相关)、QTextCodec。这些在Qt6中要么被替代,要么需要额外兼容模块。

2. 构建系统革命:从qmake到CMake的完整转换

qmake的.pro文件曾经是Qt项目的标配,但在多平台、多配置的现代开发环境中已显疲态。以下是一个典型迁移过程:

2.1 基础CMakeLists.txt框架

cmake_minimum_required(VERSION 3.21) project(MyApp LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) add_executable(MyApp main.cpp mainwindow.cpp mainwindow.h resources.qrc ) target_link_libraries(MyApp PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets )

关键改进点:

  • 自动处理机制AUTOMOC/AUTORCC/AUTOUIC替代了qmake的CONFIG += automoc
  • 精确模块控制:通过COMPONENTS明确指定所需模块,避免链接冗余库
  • 现代依赖管理:与FetchContentvcpkg等现代C++依赖管理工具无缝集成

2.2 处理复杂项目结构

对于包含子模块的大型项目,CMake的target体系展现出巨大优势:

# 主项目CMakeLists.txt add_subdirectory(core) add_subdirectory(gui) add_subdirectory(tests) # core/CMakeLists.txt add_library(Core STATIC core.cpp core.h) target_compile_definitions(Core PRIVATE CORE_EXPORT) target_link_libraries(Core PUBLIC Qt6::Core) # gui/CMakeLists.txt add_library(Gui STATIC gui.cpp gui.h) target_link_libraries(Gui PUBLIC Qt6::Gui Qt6::Widgets PRIVATE Core )

这种结构带来的好处:

  • 精确的依赖传播:避免全局include路径污染
  • 增量构建优化:修改子模块时只重新编译依赖它的部分
  • 单元测试隔离:可以单独构建和测试每个组件

3. API变更与兼容层实战

Qt6删除了约15%的旧API,但提供了完善的兼容方案。以下是三个最常见的迁移场景:

3.1 正则表达式升级:QRegExp → QRegularExpression

// Qt5风格 QRegExp rx("(\\d+)(\\s*)(\\w+)"); if (rx.indexIn(text) != -1) { QString num = rx.cap(1); QString word = rx.cap(3); } // Qt6现代写法 QRegularExpression re(R"((\d+)(\s*)(\w+))"); if (auto match = re.match(text); match.hasMatch()) { QString num = match.captured(1); QString word = match.captured(3); }

性能对比:

操作类型QRegExp (ms)QRegularExpression (ms)
简单模式匹配12.38.7
长文本搜索45.629.2
重复使用匹配78.915.4

3.2 图形渲染管线升级

Qt6中所有OpenGL相关类都已迁移到新的QRhi(Qt Rendering Hardware Interface)抽象层:

// 旧版OpenGL初始化 QGLWidget::initializeGL() { glClearColor(0, 0, 0, 1); glEnable(GL_DEPTH_TEST); } // 新版QRhi等效代码 void initialize() { QRhiGraphicsPipeline pipeline; pipeline.setDepthTest(true); pipeline.setDepthWrite(true); QRhiRenderPassDescriptor desc; desc.setClearColor(QColor(0, 0, 0)); // ... }

3.3 文本编码处理

Qt5的QTextCodec在Qt6中需要单独安装兼容模块:

# CMake中添加文本编码支持 find_package(Qt6 REQUIRED COMPONENTS Core5Compat) target_link_libraries(MyApp PRIVATE Qt6::Core5Compat )

4. 分阶段迁移策略与错误修复

我们推荐采用渐进式迁移而非一次性重写,具体分为四个阶段:

  1. 并行构建阶段(1-2周)

    • 保持原有qmake构建系统正常工作
    • 在项目根目录添加CMakeLists.txt
    • 验证基础组件能在CMake下编译
  2. 模块迁移阶段(2-4周)

    • 按依赖关系从底层开始迁移子模块
    • 使用CMake的ExternalProject集成尚未迁移的部分
    • 示例错误修复:
      # 错误:undefined reference to `QSerialPort::QSerialPort()' # 修复:添加SerialPort组件 find_package(Qt6 REQUIRED COMPONENTS SerialPort)
  3. CI/CD集成阶段(1周)

    • 更新Jenkins/GitLab CI脚本
    • 处理跨平台差异:
      if(WIN32) target_compile_definitions(MyApp PRIVATE WIN32_LEAN_AND_MEAN) elseif(APPLE) find_library(COCOA_LIBRARY Cocoa) endif()
  4. 优化与新特性阶段(持续)

    • 启用Qt6独占功能如Qt Quick 3D
    • 应用C++17现代语法重构旧代码

常见编译错误速查表:

错误信息解决方案
'QDesktopWidget' was not declared in scope改用QScreen或QWindow相关API
cannot find -lQt5::Core更新链接器参数为Qt6::Core
QFontMetricsF::width() is deprecated使用horizontalAdvance()替代
error: 'QtWin' is not a namespace name安装qt5compat模块并添加Core5Compat组件

5. 现代化改造:超越简单迁移

完成基础迁移后,这些Qt6特性值得特别关注:

  • Qt Quick 3D集成:在传统的Widgets项目中嵌入3D内容

    View3D { importScene: SceneLoader { source: "model.glb" } }
  • 改进的QML类型系统

    // 注册C++类型到QML qmlRegisterType<DataModel>("com.company", 1, 0, "DataModel"); // QML中使用 DataModel { id: model onDataChanged: console.log("Update received") }
  • 高性能并发:QRunnable + QThreadPool的现代替代方案

    QFuture<void> future = QtConcurrent::run([=]{ // 并行计算任务 }); QFutureWatcher<void> watcher; QObject::connect(&watcher, &QFutureWatcher::finished, []{ qDebug() << "Task completed"; }); watcher.setFuture(future);

迁移后的项目结构优化建议:

my_project/ ├── cmake/ # 自定义CMake模块 ├── core/ # 业务逻辑层 ├── gui/ │ ├── widgets/ # 传统Widgets界面 │ └── qml/ # QML界面组件 ├── third_party/ # 第三方依赖 └── tests/ # 测试目录 ├── unit/ # 单元测试 └── bench/ # 性能测试

在最近一个工业控制项目的迁移中,通过结合CMake的Unity Build和Qt6的预编译头技术,我们将原本45分钟的完整构建时间缩短到12分钟。更令人惊喜的是,新的构建系统使得在Windows/Linux/macOS三平台并行开发变得异常顺畅——这是旧qmake系统难以企及的。

← 返回列表