C++ Beginner's Guide: Managing Your Project Using CMake
作为一个 C++ 初学者,在学习基本语法和练习示例代码时,需要频繁使用 g++ ... input.cpp -o output && ./output
类似的命令编译和运行程序。虽然这种方式有助于熟悉 g++
,但是对于想要快速测试代码的新手来说,这种方式不够友好。
本文介绍如何利用 CMake 来管理一个 C++ 学习项目,帮助你快速实现编译和运行。
什么是 CMake?
简单来说,CMake 是一个跨平台的构建系统,它可以帮助你定义如何编译和链接你的项目,而不必关心具体的编译命令。CMake 会为你生成项目所需的 Makefile 或其他构建文件,所以你只需要关心代码即可。
如何安装 CMake?
在 macOS 中可以直接通过 homebrew 安装:
brew install cmake
本文以 CMake 3.27.4 为例。 其他操作系统的安装方式,请参考 Getting and Installing CMake on Your Computer。
如何使用 CMake 管理项目?
每个使用 CMake 的项目都需要定义 CMakeLists.txt
文件,即 CMake 的配置文件,用于告诉 CMake 如何构建你的项目。使用 CMake 管理项目的常见流程为:
- 按需配置或更新
CMakeLists.txt
- 生成构建文件:CMake 会根据
CMakeLists.txt
配置并生成项目所需的构建文件 - 调试或运行项目
下面是一个 CMakeLists.txt
的示例:
cmake_minimum_required(VERSION 3.26)
project(cpp_learning)
set(CMAKE_CXX_STANDARD 20)
add_executable(cpp_learning main.cpp)
cmake_minimum_required(VERSION 3.26)
:配置需要的最低 CMake 版本,例如3.26
。注意:CMakeLists.txt
的第一行必须是cmake_minimum_required
project(cpp_learning)
:配置项 目名称,例如cpp_learning
set(CMAKE_CXX_STANDARD 20)
:配置 C++ 版本,例如20
add_executable(cpp_learning main.cpp)
:将main.cpp
编译为可执行文件cpp_learning
在学习 C++ 的过程中,我们需要编写非常多的程序。引入 CMake 之后,只需要在 CMakeLists.txt
中添加 add_executable
即可。例如 add_executable(hello hacking_cpp/hello.cpp)
将 hacking_cpp/hello.cpp
编译为可执行文件 hello
。
在配置 CMakeLists.txt
后,需要使用 cmake
命令生成构建文件。例如下面命令:
mkdir build # Create a directory for the build
cd build
cmake .. # Generate build files using CMake, .. indicates the parent directory where CMakeLists.txt is located
make # This will execute the build files and build the project
如果使用集成 CMake 的 IDE,则无需手动运行上述 cmake ..
、make
命令。例如在 CLion 中每次更新 CMakeLists.txt
后只需要点击 Load CMake Changes 图标即可实现 cmake ..
。具体使用方法参考 Reload CMake on changes in CMakeLists.txt。
如何引入第三方库?
在 C++ 项目中,经常需要使用一些第三方库来更高效地实现某些功能。下面以使用 fmt
(一个现代 C++ 格式化库)为例,介绍如何更新 CMakeLists.txt
并在程序中使用 fmt
。
源码编译
当你希望从源码编译 fmt
时,首先需要将 fmt
源码下载到你的项目目录中。下面提供了 git subtree
和 git submodule
两种方式:
- git subtree
- git submodule(不推荐)
cd cpp_learning
git subtree add --prefix thirdparty/fmt https://github.com/fmtlib/fmt.git master --squash
输出结果示例:
git fetch https://github.com/fmtlib/fmt.git master
remote: Enumerating objects: 32297, done.
remote: Counting objects: 100% (993/993), done.
remote: Compressing objects: 100% (85/85), done.
remote: Total 32297 (delta 928), reused 934 (delta 900), pack-reused 31304
Receiving objects: 100% (32297/32297), 13.73 MiB | 12.03 MiB/s, done.
Resolving deltas: 100% (21894/21894), done.
From https://github.com/fmtlib/fmt
* branch master -> FETCH_HEAD
Added dir 'thirdparty/fmt'
此时查看 git log
可以发现 fmt
被 squash 为一个 commit 以及新增了一个 merge commit:
commit dd05d0f6812ddcfb72d5e1425e7bad88f424451f
Merge: e5b1c472 20d0c586
Author: Oreo
Date: Mon Sep 4 21:37:57 2023 +0800
Merge commit '20d0c58622d07bf66f125efff52cc440b88fe2eb' as 'thirdparty/fmt'
commit 20d0c58622d07bf66f125efff52cc440b88fe2eb
Author: Oreo
Date: Mon Sep 4 21:37:57 2023 +0800
Squashed 'thirdparty/fmt/' content from commit e8259c52
git-subtree-dir: thirdparty/fmt
git-subtree-split: e8259c5298513e8cdbff05ce01c46c684fe758d8
之后需要更新 fmt
时,只需要运行 git subtree pull
:
git subtree pull --prefix thirdparty/fmt https://github.com/fmtlib/fmt.git master --squash
cd cpp_learning
mkdir thirdparty
git submodule add https://github.com/fmtlib/fmt fmt
输出结果示例:
Cloning into 'cpp-learning/thirdparty/fmt'...
remote: Enumerating objects: 32886, done.
remote: Counting objects: 100% (1261/1261), done.
remote: Compressing objects: 100% (103/103), done.
remote: Total 32886 (delta 1188), reused 1188 (delta 1149), pack-reused 31625
Receiving objects: 100% (32886/32886), 14.09 MiB | 23.39 MiB/s, done.
Resolving deltas: 100% (22295/22295), done.
查看 git status
:
git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitmodules
new file: fmt
新增了 fmt
和 .gitmodules
。其中 .gitmodules
内容如下:
[submodule "fmt"]
path = fmt
url = https://github.com/fmtlib/fmt
之后需要更新 fmt
时,只需要运行 git submodule update
:
git submodule update --remote
接着更新 CMakeLists.txt
,需要告诉 CMake 如何找到 fmt
并链接到 hello
应用程序。可以通过 add_subdirectory
和 target_link_libraries
实现查找和链接:
cmake_minimum_required(VERSION 3.26)
project(cpp_learning)
set(CMAKE_CXX_STANDARD 20)
add_subdirectory(thirdparty/fmt)
add_executable(cpp_learning main.cpp)
add_executable(hello hacking_cpp/hello.cpp)
target_link_libraries(hello fmt::fmt)
add_subdirectory
:指定fmt
源码及其CMakeLists.txt
所在位置,CMake 会直接将fmt
的CMakeLists.txt
包含到当前的构建中target_link_libraries
:将查找到的fmt
库链接到目标应用程序hello
二进制分发
当你通过 homebrew 或其他包管理器安装 fmt
时,可以通过 find_package
和 target_link_libraries
实现查找和链接:
cmake_minimum_required(VERSION 3.26)
project(cpp_learning)
set(CMAKE_CXX_STANDARD 20)
find_package(fmt REQUIRED)
add_executable(cpp_learning main.cpp)
add_executable(hello hacking_cpp/hello.cpp)
target_link_libraries(hello fmt::fmt)
find_package
:搜索并加载系统中的fmt
库target_link_libraries
:将查找到的fmt
库链接到目标应用程序hello
使用 fmt
实现格式化输出示例
下面示例代码使用了 std::cout
、fmt::print
和 fmt::println
三种方式进行字符串的输出:
#include <fmt/format.h>
#include <iostream>
int main() {
std::cout << "std::cout: Hello World\n";
fmt::print("fmt::print: Hello!");
fmt::println("fmt::println: Hello, {}, {}, and {}", 0, 1, 2);
fmt::print("fmt::print: Bye");
return 0;
}
运行结果如下:
std::cout: Hello World
fmt::print: Hello!fmt::println: Hello, 0, 1, and 2
fmt::print: Bye