C++ project organisation (with gtest, cmake and doxygen)

rozzy picture rozzy · Nov 23, 2012 · Viewed 42.8k times · Source

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.

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

and the second being:

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

Which would you recommend and why?

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?

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?

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

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

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

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

Answer

pmr picture pmr · Nov 23, 2012

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.

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.

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.

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

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).

You will end up with something like this:

└── 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

where

  • (1) configures dependencies, platform specifics and output paths
  • (2) configures the library you are going to build
  • (3) configures the test executables and test-cases

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: 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.

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.

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.