基于CMake的C++工程文件

本文详细阐述了C++项目文件结构、CMakeLists.txt的编写原则,以及如何利用CMake进行编译设置和外部库管理。从include文件夹的组织、src和libs文件夹的功能划分,到如何通过CMake自动化编译流程,确保项目的可维护性和第三方库的便捷使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

在开发C++大工程时,有两件事情要注意:

  • 维护项目结构
  • 处理第三方库

文件结构

通常的c++工程结构如下:

  • CMakeLists.txt
  • include文件夹
  • src文件夹
  • libs文件夹
  • tests文件夹

inlucde文件夹

传统上,include文件夹是用于放header files, 但是modern practice 建议include文件夹必须strictly contain headers that need to be exposed publicly. 我们在include文件夹下,还特地加了一个与project名字相同的文件夹。这样做的目的是,由于include文件夹主要是为了方便外用的(供别人调用你写的这个library),所以当别人调用该project用于外用时是这样的:#include <Project_Name/public_header.h>,而不是#include <public_header.h>的。

src文件夹

src文件夹包含所有的源代码,以及所有那些仅用于internal use的header files。基本上,如果你注意third-party libraries时,基本都有类似的结构。

libs文件夹

libs文件夹包含了所有我们需要用到的third-party libraries。通常我们有两种方式来使用third-party libraries in C++:Static或者Dynamic。libs文件夹只包含通过static方式使用的third-party libraries。

tests文件夹

用于做unit tests或者integration tests的代码都放在这里。

CMakeLists.txt文件

CMakeLists.txt是告诉CMake命令what to do的配置文件。需要注意的是CMake is not a build system, but a build system的生成器. 要理解这句话什么意思,你需要懂得Make and CMake的区别。

为了很好地理解,让我们先git clone该C++工程https://github.com/AakashMallik/sample_cmake。通常你从github上clone一个C++工程,为了运行该工程,你需要做一下在terminal走以下五步:

cd path_of_the_downloaded_project
mkdir build
cd build
cmake ..
make

mkdir build在做什么
本质上只是创建了一个文件夹。所以Technically speaking, you can get away without creating this directory. This is done just to keep your code clean and mess-free. So for now take my word for it and try to understand what is this mess I am talking about.

cmake …在做什么
CMake单纯是用于生成Make file。那么Make file是用来干什么的呢?
首先我们知道g++编译工作一般分为四个步骤:

  • 预处理(Preprocessing): g++ -E test.cpp -o test.i //生成预处理后的.i文件, -E代表只激活预处理
  • 编译(Compilation):g++ -S test.i -o test.s //生成汇编.s文件,-S代表只激活预处理和编译
  • 汇编(Assembly):g++ -c test.s -o test.o //生成二进制.o文件,-c只激活预处理,编译,和汇编
  • 链接(Linking):g++ test.o -o test.out //生成二进制.out可执行文件

假设我们有main.cpp,a.cpp,a.h,b.cpp,b.h,并且main.cpp包含了main() function,并且该main() function就依赖于a.cpp & b.cpp。那么make file相当于要完成两步:

  • compiling(包含了预处理、编译和汇编): 即对依赖的文件a.cpp & b.cpp,和main.cpp生成对应的三个目标文件a.o,b.o,main.o
  • linking: 将a.o,b.o与main.o联系起来生成binary/executable file

具体命令是:

g++ -c a.cpp      //-c只激活预处理,编译,和汇编
g++ -c b.cpp      //-c只激活预处理,编译,和汇编
g++ -c main.cpp   //-c只激活预处理,编译,和汇编
g++ a.o b.o main.o -o binary //link目标文件,生成可执行二进制文件

但问题是每次你对其中一个任何代码有更新,你都要重新compile和link。另一个问题是,如果你的工程很大,有几千个cpp代码文件呢?于是就想出一个办法:调用a build system来自动化完成这件事情,于是make这个工具应运而生。有了这个make工具,你只需要:

  • write a make file and tell it what commands to execute
  • run the make tool pointing it to the location of the make file.

但是这里又有新的问题,写make file很麻烦,于是就有了cmake这个工具。它通过写更为简单CMakeLists.txt(而不是直接写make file)用来自动生成make file。所以我们说cmake是build system(指的是make file)的生成器。所以刚才我们在运行一个工程文件需要走的步骤中的”cmake …”就是在build的上一层目录path_of_the_downloaded_project路径下找到CMakeLists.txt,然后cmake工具依据CMakeLists.txt在build路径下来生成make file。

cd path_of_the_downloaded_project
mkdir build
cd build
cmake ..
make

总结:我们通过cmake命令根据提供的CMakeLists.txt文件路径信息(当不指明路径,就默认是当前路径。)来读CMakeLists.txt文件,在当前路径下生成了make file(a build system),然后make命令根据提供的make路径信息(当不指明路径,就默认是当前路径)来读make file,将整个c++工程在当前路径下生成对应的二进制可执行文件。

如何写CMakeLists.txt?

一个好的c++工程应该具备一下是三个特点:

  • 方便你随时编译并生成二进制可执行文件
  • 方便别人调用你的工程当做third-party library(Expose a header file to let people use your code as a third-party library)
  • Able to use third-party libraries in your code.

我们继续用https://github.com/AakashMallik/sample_cmake项目来做为例子

下面将展示如何手动和自动化两种方式(通过写CMakeLists.txt文件用cmake和make来做)来build整个工程。

手动来build生成二进制可执行文件

g++ ../src/game_engine.cpp ../src/game_interface.cpp ../src/main.cpp -I ../src -I ../include -I ../libs/Logger/include -I ../libs/Randomize/include -L ../libs/Logger -l logger -L ../libs/Randomize -l randomize

以上命令可以看做是有四步:

  • Path to the files that you have written and wish to compile.
  • Path to all the header files, including the ones used from 3rd party libraries.
  • Paths to third-party libraries’ .a files.
  • Names of the .a files that we want the compiler to link with our code.

自动化build生成二进制可执行文件
CMakeLists.txt文件可以分为6个主要部分:

  • Flags
  • Files
  • Include
  • Targets
  • External libraries
  • Unit Testing

第一个件事是写出cmake tool要求的版本,如果你的电脑装了比较旧的cmake tool,那么我们就无法cmake成功,需要更新了。

第二件事就是定义project name(不一定与main.cpp的名字相同)

第三件事指出flags,就是告诉cmake命令你打算用哪个compiler和compiler的版本来build你的project。如果没有写明flags,cmake会自动找gcc compiler,即 it will pick the best fit on its own.

第四件事是include_directories。这里需要给出全部所要用到的head files所在的路径,那么显然也包含了要调用的third-party libraries的head files所在的路径。

第五件事是add_executable,用来告诉cmake命令你要的输出是什么。我们这里需要的是binary的,后面列出的三个文件就是你的源代码文件, the same way as you do while compiling them manually.

第六件事是add_subdirectory,add_library。这里就是前面提到的make file要做的两步compiling和linking两步中的linking。刚才我们在第四件事上已经给出third-party libraries的head files在哪里,而我们知道linking这一步是需要给出third-party libraries的文件(By convention, library files are named with ‘lib’ as a prefix or a suffix, 例如loggerlib,或者liblogger)在哪里,所以add_subdirectory就是加third-party libraries的路径。

在c++工程中,可以使用两种类型的libraries:

  • static ( .a files ): also known as an archive.
  • dynamic ( .so files ): also called a shared library

如何创建你自己的static third-party libraries是一个重要话题,但是这里先假设我们要用到的两个libraries(Logger and Randomize)是static。而static third-party libraries可以用两种类型:

第一类:CMakeLists.txt + include + src

add_subdirectory( ./libs/Logger )                       
target_link_libraries( binary logger )
  • 这里用到的Logger就是这类
  • add the path of Logger directory as a subdirectory
  • tell the compiler to link it to your output.
  • CMake will automatically look for a CMakeLists.txt file inside the subdirectory and run it. It will use the .a file(static library in Linux) to link with your output as mentioned in the second line.
  • the file it will be looking for is not logger.a, but liblogger.a or loggerlib.a.

第二类:.a file + include

这里用到的Randomize就是这类

  • In this case, the library file has already been compiled for you and you don’t need CMake to do it for you.
  • But adding this directory as a subdirectory won’t work as CMake will start looking for a CMakeLists.txt inside the library’s directory and as it doesn’t have one, it will throw an error and won’t compile.
  • The first one basically tells CMake that we are using a static library name librandomize.a or randomizelib.a.
  • The second line specifies the path from where it resides. I have used the ${CMAKE_SOURCE_DIR} to demonstrate the use of internal CMake variable. That variable is the path to the CMakeLists.txt in your root directory; the one we run to build our binary.
  • The third line as you might have guessed, links the library to our output.

总结

mkdir build
cd build
cmake ..
make

And just like that you have your output — binary. You can run it now!

./binary
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点PY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值