第1章:Qt项目结构分析

做Qt开发这么多年,我接手过的项目少说也有几十个。说实话,每次看到新项目的目录结构,心里都会咯噔一下——有的项目文件堆得跟垃圾场似的,有的又规整得像教科书。今天咱们就来聊聊,一个合格的Qt项目到底该怎么组织。

1.1 现有项目目录结构剖析

先看一个典型的「反面教材」。我曾经接手过一个工业控制项目,目录长这样:

MyProject/
├── main.cpp
├── widget.cpp
├── widget.h
├── dialog.cpp
├── dialog.h
├── utils.cpp
├── utils.h
├── config.ini
├── image1.png
├── image2.png
├── style.qss
├── database.db
└── ...(还有30多个文件散落在这里)

你发现问题了吗?所有文件都堆在根目录下。找个头文件得翻半天,想改个资源文件更是大海捞针。我个人习惯,项目一启动就先搭好目录骨架,不然后面重构起来真要命。

那一个好的目录结构长什么样?我给大家看个参考:

MyProject/
├── src/                    # 源代码
│   ├── app/               # 应用入口
│   ├── core/              # 核心业务逻辑
│   ├── ui/                # 界面相关
│   │   ├── widgets/       # 自定义控件
│   │   ├── dialogs/       # 对话框
│   │   └── pages/         # 页面
│   ├── data/              # 数据层
│   └── utils/             # 工具类
├── resources/              # 资源文件
│   ├── images/
│   ├── styles/
│   └── translations/
├── tests/                  # 单元测试
├── docs/                   # 文档
├── CMakeLists.txt          # 构建文件
└── README.md

核心原则:按功能模块分目录,而不是按文件类型。把相关的.h、.cpp、.ui文件放在一起,而不是把所有.h扔一个文件夹。

1.2 模块划分原则

模块划分这事儿,说白了就是「高内聚、低耦合」。我在项目中遇到过最头疼的情况,就是两个模块互相引用,改一个地方崩一片。

给大家几个实用的划分原则:

  • 单一职责:一个模块只做一件事。比如网络模块只管收发数据,别掺和界面逻辑
  • 接口清晰:模块之间通过信号槽或接口类通信,别直接访问对方的私有成员
  • 可替换性:每个模块都应该能独立测试、独立替换

举个例子,一个典型的Qt项目可以这样分模块:

模块名 职责 依赖关系
core 业务逻辑、数据模型 无
ui 界面展示、用户交互 依赖core
network 网络通信、协议解析 依赖core
database 数据持久化、ORM 依赖core
utils 通用工具函数 无

小技巧:模块之间的依赖关系应该是单向的。比如ui可以依赖core,但core绝对不能依赖ui。这样以后换界面库(比如从QWidget换成QML)才不会伤筋动骨。

1.3 依赖关系梳理

依赖关系这东西,就像人际关系——理不清就会乱成一锅粥。我曾经在一个项目里发现,A模块依赖B,B依赖C,C又依赖A,形成了循环依赖。编译倒是能过,但每次改代码都得小心翼翼,生怕触发连锁反应。

怎么梳理依赖关系?我一般用三步走:

  1. 画依赖图:把每个模块的#include列出来,画成有向图
  2. 找循环:用工具(比如CMake的--graphviz选项)检测循环依赖
  3. 拆解:把循环依赖中的公共部分提取出来,放到新的模块里

下面我用SVG画一个典型的依赖关系图,大家感受一下:

core ui network database utils(被所有模块依赖) 实线:直接依赖 虚线:间接依赖

注意:千万别让core模块依赖任何界面相关的东西。我见过有人把QWidget的头文件include到core里,结果整个项目耦合得死死的,想换界面库?门儿都没有。

1.4 构建系统评估

Qt的构建系统,说白了就两个选择:qmake和CMake。嗯,这里要注意,qmake虽然简单,但已经快被Qt官方抛弃了。我个人强烈建议新项目直接用CMake。

为什么?给你看个对比:

特性 qmake CMake
跨平台支持 一般 优秀
第三方库集成 麻烦 简单(find_package)
测试框架集成 不支持 支持(CTest)
IDE支持 Qt Creator VS、CLion、Qt Creator
模块化支持 弱 强(add_subdirectory)

一个典型的CMakeLists.txt长这样:

cmake_minimum_required(VERSION 3.16)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets Network Sql)

add_subdirectory(src/core)
add_subdirectory(src/ui)
add_subdirectory(src/network)
add_subdirectory(src/database)

target_link_libraries(MyApp
    core_lib
    ui_lib
    network_lib
    database_lib
)

避坑指南:我曾经在迁移一个老项目时,发现qmake的.pro文件里写了各种奇奇怪怪的路径。换成CMake后,用target_include_directories和target_link_libraries,依赖关系一目了然。嗯,真香。

最后说一句,构建系统的选择其实没那么玄乎。如果你项目小、团队就你一个人,用qmake也不是不行。但如果你打算长期维护、多人协作,CMake绝对是更明智的选择。

好了,项目结构这块儿就聊到这儿。记住一句话:好的结构是重构出来的,不是设计出来的。别怕改,改着改着就顺了。