2. 搭建跨平台开发环境

跨平台工程的第一道门槛不是写代码,而是让同一份源码在三套系统上都能被正确编译、链接和运行。这一节我们把“环境”拆成四层来看:编译器与工具链、构建系统、依赖管理、工程规范。四层各自独立,又必须协同工作,任何一层选错,后面都会以“在我机器上能跑”的形式反复折磨团队。

一、编译器与工具链:三平台的实际选择

Windows 上主流有两条路线。第一条是 MSVC(Visual Studio 的 C++ 编译器),它随 Visual Studio 安装,命令行入口是 cl.exe,链接器是 link.exe,配套调试器是 cdb 或 Visual Studio 调试器。第二条是 MinGW-w64 或 LLVM/Clang,它们提供类 Unix 的命令行体验,产物是 PE 格式。MSVC 对 Windows API、COM、MSVC ABI 兼容性最好,是 Windows 原生开发的首选;MinGW 适合希望统一 GCC 风格命令行的团队,但要注意它与 MSVC 的 ABI 不兼容,混用静态库会出问题。

Linux 上通常直接使用发行版自带的 GCC 或 Clang。以 Ubuntu 22.04 为例,g++ --version 一般显示 GCC 11,clang++ --version 显示 Clang 14。Linux 的链接器是 ld 或更快的 lld、gold。调试器是 gdb 或 lldb。Linux 生态里还有一个关键点:glibc 版本决定了二进制能跑在哪些发行版上,因此发布时常用容器或较老的基线镜像来构建。

macOS 上编译器来自 Xcode Command Line Tools,clang++ 实际是 Apple Clang,链接器是 ld64,调试器是 lldb。macOS 同时支持 x86_64 和 arm64(Apple Silicon),可以用 -arch x86_64 -arch arm64 生成通用二进制(Universal Binary)。需要注意 Apple Clang 与上游 LLVM Clang 在版本号和部分特性上并不完全对应。

三平台工具链的差异可以用下面这张图快速对照,图中同时标出了每个平台的关键命令与产物格式。

三平台编译器与工具链对照 Windows 编译器 MSVC (cl.exe) MinGW-w64 / Clang 链接器 link.exe / lld-link 调试器 cdb / VS Debugger 产物格式 PE (.exe / .dll) 包管理器 vcpkg / Conan 注意 MSVC 与 MinGW ABI 不兼容 Linux 编译器 GCC (g++) / Clang 链接器 ld / gold / lld 调试器 gdb / lldb 产物格式 ELF (.out / .so) 包管理器 apt / dnf / pacman 注意 glibc 版本决定运行兼容性 macOS 编译器 Apple Clang (clang++) 链接器 ld64 / lld 调试器 lldb 产物格式 Mach-O (.app / .dylib) 包管理器 Homebrew / vcpkg 注意 x86_64 与 arm64 需通用二进制 统一抽象层:CMake 生成各平台原生构建文件(MSBuild / Makefile / Ninja / Xcode)

二、CMake:跨平台构建的统一入口

CMake 不直接编译代码,而是根据 CMakeLists.txt 生成各平台的原生构建文件:Windows 上生成 Visual Studio 工程或 Ninja 文件,Linux 上生成 Makefile 或 Ninja,macOS 上生成 Xcode 工程或 Ninja。这样同一份构建描述可以在三平台复用。

一个最小可用的跨平台 CMakeLists.txt 如下,它同时处理了 C++ 标准、可执行目标、平台相关链接库和安装规则:

cmake_minimum_required(VERSION 3.20)
project(CrossDemo LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(cross_demo
    src/main.cpp
    src/platform/platform_win.cpp
    src/platform/platform_linux.cpp
    src/platform/platform_mac.cpp
)

target_include_directories(cross_demo PRIVATE include)

if(WIN32)
    target_link_libraries(cross_demo PRIVATE ws2_32)
elseif(APPLE)
    target_link_libraries(cross_demo PRIVATE "-framework CoreFoundation")
elseif(UNIX)
    target_link_libraries(cross_demo PRIVATE pthread dl)
endif()

install(TARGETS cross_demo RUNTIME DESTINATION bin)

实际使用时,推荐采用“源码外构建”,即构建目录与源码目录分离。典型命令是:

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel

其中 -G Ninja 指定生成器,Ninja 在三平台都可用且速度快;-DCMAKE_BUILD_TYPE=Release 在单配置生成器(Makefile、Ninja)下生效,而 Visual Studio 和 Xcode 是多配置生成器,需要用 --config Release 指定。这是初学者最容易踩的坑之一。

三、包管理器:依赖从哪来

跨平台项目常见的依赖管理方案有三类。第一类是系统包管理器:Linux 用 apt install libssl-dev,macOS 用 brew install openssl,Windows 用 vcpkg 或手动下载。第二类是 C++ 专用包管理器 vcpkg 和 Conan,它们能跨平台拉取源码并编译,且与 CMake 集成良好。第三类是 CMake 的 FetchContent,直接在配置阶段下载并纳入构建。

以 vcpkg 为例,典型流程是:

git clone https://github.com/microsoft/vcpkg
./vcpkg/bootstrap-vcpkg.sh        # Windows 用 bootstrap-vcpkg.bat
./vcpkg/vcpkg install fmt spdlog
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake

这样 find_package(fmt CONFIG REQUIRED) 就能直接找到依赖。Conan 的思路类似,但使用 conanfile.txt 描述依赖,并通过 conan install 生成 CMake 工具链文件。选择哪一类取决于团队:如果依赖少且系统包版本可控,系统包管理器最省事;如果依赖多、需要锁定版本和跨平台一致,vcpkg 或 Conan 更合适。

四、统一代码风格与目录结构

代码风格不统一会让 diff 噪音大、review 效率低。跨平台团队通常用 .clang-format 统一格式,用 .clang-tidy 做静态检查,用 .editorconfig 统一缩进和换行。一个可用的 .clang-format 片段如下:

BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
AllowShortFunctionsOnASingleLine: Empty
PointerAlignment: Left

配合 clang-format -i src/*.cpp include/*.h 即可批量格式化。建议在 CI 中加一步 clang-format --dry-run --Werror,格式不合规直接失败,避免风格问题进入主干。

目录结构方面,推荐按“接口与实现分离、平台代码隔离”来组织:

project/
├── CMakeLists.txt
├── cmake/                  # 自定义 Find 模块与工具链文件
├── include/project/        # 对外公开头文件
├── src/
│   ├── core/               # 跨平台核心逻辑
│   └── platform/           # 平台相关实现
│       ├── platform_win.cpp
│       ├── platform_linux.cpp
│       └── platform_mac.cpp
├── tests/                  # 单元测试
├── third_party/            # 第三方源码(可选)
└── .clang-format

平台相关代码通过统一的头文件接口暴露,例如 include/project/platform.h 声明 std::string get_platform_name();,三个 platform_*.cpp 各自实现,CMake 根据 WIN32、APPLE、UNIX 条件选择源文件。这样核心逻辑不包含任何 #ifdef _WIN32,可读性和可测试性都更好。

建议把编译器最低版本、CMake 最低版本、C++ 标准写进项目 README 和 CI 配置,并在 CMakeLists.txt 中用 cmake_minimum_required 与 CMAKE_CXX_STANDARD 强制约束,避免“某台机器能编、另一台编不过”。
不要在三平台之间共享构建目录。Windows 的路径分隔符、大小写不敏感文件系统,与 Linux 的大小写敏感、macOS 默认大小写不敏感但可配置的行为都不同。构建目录应加入 .gitignore,每平台独立生成。