C++ 项目组织(使用 gtest、cmake 和 doxygen)

2021-12-26 00:00:00 doxygen cmake c++ googletest

我是编程新手,所以我决定从用 C++ 编写一个简单的向量类开始.但是,我希望从一开始就养成良好的习惯,而不是在以后尝试修改我的工作流程.

I am new to programming in general so I decided that I would start by making a simple vector class in C++. However I would like to get in to good habits from the start rather than trying to modify my workflow later on.

我目前只有两个文件 vector3.hppvector3.cpp.随着我对所有内容越来越熟悉,这个项目将慢慢开始发展(使其更像是一个通用的线性代数库),所以我想采用标准"项目布局,让以后的生活更轻松.因此,环顾四周后,我找到了两种组织 hpp 和 cpp 文件的方法,第一种是:

I currently have only two files vector3.hpp and vector3.cpp. This project will slowly start to grow (making it much more of a general linear algebra library) as I become more familiar with everything, so I would like to adopt a "standard" project layout to make life easier later on. So after looking around I have found two ways to go about organizing hpp and cpp files, the first being:

project
└── src
    ├── vector3.hpp
    └── vector3.cpp

第二个是:

project
├── inc
│   └── project
│       └── vector3.hpp
└── src
    └── vector3.cpp

你会推荐哪个,为什么?

Which would you recommend and why?

其次,我想使用 Google C++ 测试框架对我的代码进行单元测试,因为它看起来相当容易使用.您是否建议将其与我的代码捆绑在一起,例如在 inc/gtestcontrib/gtest 文件夹中?如果捆绑,您是否建议使用 fuse_gtest_files.py 脚本来减少数量或文件,还是保持原样?如果没有捆绑,这个依赖是如何处理的?

Secondly I would like to use the Google C++ Testing Framework for unit testing my code as it seems fairly easy to use. Do you suggest bundling this with my code, for example in a inc/gtest or contrib/gtest folder? If bundled, do you suggest using the fuse_gtest_files.py script to reduce the number or files, or leaving it as is? If not bundled how is this dependency handled?

在编写测试时,这些测试通常是如何组织的?我想为每个类创建一个 cpp 文件(例如 test_vector3.cpp),但所有文件都编译为一个二进制文件,以便它们可以轻松地一起运行?

When it comes to writing tests, how are these generally organized? I was thinking to have one cpp file for each class (test_vector3.cpp for example) but all compiled in to one binary so that they can all be run together easily?

由于 gtest 库通常是使用 cmake 和 make 构建的,我在想我的项目也这样构建是否有意义?如果我决定使用以下项目布局:

Since the gtest library is generally build using cmake and make, I was thinking that it would make sense for my project to also be built like this? If I decided to use the following project layout:

├── CMakeLists.txt
├── contrib
│   └── gtest
│       ├── gtest-all.cc
│       └── gtest.h
├── docs
│   └── Doxyfile
├── inc
│   └── project
│       └── vector3.cpp
├── src
│   └── vector3.cpp
└── test
    └── test_vector3.cpp

CMakeLists.txt 应该如何看待,以便它可以只构建库或库和测试?此外,我还看到了很多具有 buildbin 目录的项目.构建是否发生在构建目录中,然后二进制文件移出到 bin 目录?测试和库的二进制文件会在同一个地方吗?或者将其结构如下更有意义:

How would the CMakeLists.txt have to look so that it can either build just the library or the library and the tests? Also I have seen quite a few projects that have a build and a bin directory. Does the build happen in the build directory and then the binaries moved out in to the bin directory? Would the binaries for the tests and the library live in the same place? Or would it make more sense to structure it as follows:

test
├── bin
├── build
└── src
    └── test_vector3.cpp

我还想使用 doxygen 来记录我的代码.是否有可能让它与 cmake 和 make 一起自动运行?

I would also like to use doxygen to document my code. Is it possible to get this to automatically run with cmake and make?

抱歉问了这么多问题,但我还没有找到一本关于 C++ 的书来令人满意地回答这些类型的问题.

Sorry for so many questions, but I have not found a book on C++ that satisfactorily answers these type of questions.

推荐答案

C++ 构建系统有点像魔术,而且项目越老你可以找到越奇怪的东西,所以很多东西并不奇怪的问题出现.我将尝试一一解决问题,并提及有关构建 C++ 库的一些一般性事项.

C++ build systems are a bit of a black art and the older the project the more weird stuff you can find so it is not surprising that a lot of questions come up. I'll try to walk through the questions one by one and mention some general things regarding building C++ libraries.

在目录中分离头文件和 cpp 文件.这只是如果您正在构建应该使用的组件,则必不可少作为一个库而不是一个实际的应用程序.你的标题是用户与您提供的内容进行交互的基础,并且必须安装.这意味着它们必须在一个子目录中(没有人想要很多标题都在顶级 /usr/include/) 和你的标题必须能够在这样的设置中包含自己.

Separating headers and cpp files in directories. This is only essential if you are building a component that is supposed to be used as a library as opposed to an actual application. Your headers are the basis for users to interact with what you offer and must be installed. This means they have to be in a subdirectory (no-one wants lots of headers ending up in top-level /usr/include/) and your headers must be able to include themselves with such a setup.

└── prj
    ├── include
    │?? └── prj
    │??     ├── header2.h
    │??     └── header.h
    └── src
        └── x.cpp

效果很好,因为包含路径很有效,您可以轻松使用安装目标的通配符.

works well, because include paths work out and you can use easy globbing for install targets.

捆绑依赖:我认为这在很大程度上取决于用于定位和配置依赖项的构建系统以及如何依赖于您的代码在单个版本上.这也取决于如何您的用户是否能够以及在他们的系统上安装依赖项的难易程度平台.CMake 为 Google 提供了一个 find_package 脚本测试.这让事情变得容易多了.我只会捆绑必要时避免使用.

Bundling dependencies: I think this largely depends on the ability of the build system to locate and configure dependencies and how dependent your code on a single version is. It also depends on how able your users are and how easy is the dependency to install on their platform. CMake comes with a find_package script for Google Test. This makes things a lot easier. I would go with bundling only when necessary and avoid it otherwise.

如何构建:避免源代码构建.CMake 使用源代码构建简单,它让生活更轻松.

How to build: Avoid in-source builds. CMake makes out of source-builds easy and it makes life a lot easier.

我想您还想使用 CTest 为您的系统运行测试(它还带有对 GTest 的内置支持).一个重要的决定目录布局和测试组织将是:你最终得到子项目?如果是这样,则在设置 CMakeLists 时需要做更多工作并且应该将您的子项目拆分为子目录,每个子目录都有其自己的 includesrc 文件.甚至可能是他们自己的 doxygen 运行和输出(组合多个 doxygen 项目是可能的,但并不容易或漂亮).

I suppose you also want to use CTest to run tests for your system (it also comes with build-in support for GTest). An important decision for directory layout and test organization will be: Do you end up with subprojects? If so, you need some more work when setting up CMakeLists and should split your subprojects into subdirectories, each with its own include and src files. Maybe even their own doxygen runs and outputs (combining multiple doxygen projects is possible, but not easy or pretty).

你会得到这样的结果:

└── prj
    ├── CMakeLists.txt <-- (1)
    ├── include
    │?? └── prj
    │??     ├── header2.hpp
    │??     └── header.hpp
    ├── src
    │?? ├── CMakeLists.txt <-- (2)
    │?? └── x.cpp
    └── test
        ├── CMakeLists.txt <-- (3)
        ├── data
        │?? └── testdata.yyy
        └── testcase.cpp

哪里

  • (1) 配置依赖项、平台细节和输出路径
  • (2) 配置你要构建的库
  • (3) 配置测试可执行文件和测试用例

如果您有子组件,我建议添加另一个层次结构,并为每个子项目使用上面的树.然后事情变得棘手,因为您需要决定是子组件搜索和配置它们的依赖项,还是在顶层执行此操作.这应该根据具体情况来决定.

In case you have sub-components I would suggest adding another hierarchy and use the tree above for each sub-project. Then things get tricky, because you need to decide if sub-components search and configure their dependencies or if you do that in the top-level. This should be decided on a case-by-case basis.

Doxygen:在你成功完成配置舞蹈之后doxygen,使用 CMake add_custom_command 添加一个文档目标.

Doxygen: After you managed to go through the configuration dance of doxygen, it is trivial to use CMake add_custom_command to add a doc target.

我的项目就是这样结束的,我也看到了一些非常相似的项目,但当然这并不能完全治愈.

This is how my projects end up and I have seen some very similar projects, but of course this is no cure all.

附录 在某些时候你会想要生成一个 config.hpp包含版本定义的文件,也可能是某个版本的定义控制标识符(Git 哈希或 SVN 修订号).CMake 有模块来自动查找该信息并生成文件.您可以使用 CMake 的 configure_file 替换变量CMakeLists.txt 中定义了变量的模板文件.

Addendum At some point you will want to generate a config.hpp file that contains a version define and maybe a define to some version control identifier (a Git hash or SVN revision number). CMake has modules to automate finding that information and to generate files. You can use CMake's configure_file to replace variables in a template file with variables defined inside the CMakeLists.txt.

如果您正在构建库,您还需要一个导出定义正确理解编译器之间的差异,例如__declspec 在 MSVC 上和 GCC/clang 上的 visibility 属性.

If you are building libraries you will also need an export define to get the difference between compilers right, e.g. __declspec on MSVC and visibility attributes on GCC/clang.

相关文章