Skip to main content

Choosing a Modern C++ stack

I'm starting a new project in C++, but I've run into a pair of questions before start:

  1. Which build system should I use?

  2. Which unit testing framework?

Choosing Build System (Meson)

I have used before Make, Maven, Scons, Gradle and Autotools, but I have some reasons to try find something else:


It is not easy to configure and maintain. There are several configuration files and several configuration steps.


C++ feature is still incubating. Not very fast. You can check a similar example project at Build C++ project with Gradle.


I don't love the syntax. Files tends to get messy as project grows.


It is just slower and not as easy to understand than Meson.


It is slow and you might end up "Javatizing" your C++ project structure.


I've listed just things I don't like, those projects have other great features.

After discarding previous ones, I'm considering Meson or CMake. Both are fast build systems:

Although Meson is written in Python, it generates a Ninja build project. First time you configure the project you have to run Meson, but for building or testing you are actually running Ninja.

CMake is also able to generate Ninja files among other formats, check CMake generators documentation for more information.

CMake vs Meson


It has a big advantage over Meson, it is mature and widely used in many projects, which means there are many examples and it will fulfill your C++ project building needs.


It is a young project compared with CMake, but it is growing quite fast and it has been adopted in other big projects like Gnome, they have an initiative to port from Autotools to Meson.

Finally I've chosen Meson because syntax is really clear to me, when I read file I can quickly understand what is happening during build process.

$ meson build . # first time you configure the project
$ cd build
$ ninja build   # each time you build it
$ ninja test    # each time you run tests

I've found two interesting comparisons about available C++ build systems, they might be a little be biased because those comparisons come from Meson and Scons.

Unit Testing Framework

I have used some xUnit based libraries like UnitTest++, CppUTest or Google Test which match perfectly with Google Mock. If you want a safe bet that fulfills almost of your testing needs I highly recommend Google Test.

But time ago I found a testing framework with some interesting features, Catch:

  • It is just a header file with no external dependencies, so very easy to start (wget + include downloaded file).

  • You can use normal unit test style or BDD-style

If you want to know more about Catch, I recommend you to give it a try, it is a matter of 2 minutes to have a simple example up and running. You can also read some interesting articles like Why do we need yet another C++ test framework? or Testing C++ With A New Catch.

doctest: A Catch alternative

There is another testing framework named doctest, with same benefits as Catch, but it promises to be faster and lighter (benchmark results) than Catch.

doctest is modeled after Catch and some parts of the code have been taken directly, but there are differences.

It hasn't been easy to decide, both are really similar, following you can see differences:

@@ -1,12 +1,12 @@
-#define CATCH_CONFIG_MAIN // It tells Catch to provide a main() - only do this in one cpp file

-#include "catch.hpp"
+#include "doctest.h"
#include "Uuid.h"
#include <string>

constexpr int MAX_ITERS = 100;

-TEST_CASE("Uuid", "[uuid]")
for (int i = 0; i < MAX_ITERS; i++)
@@ -26,7 +26,7 @@ TEST_CASE("Uuid", "[uuid]")

// BDD style

-SCENARIO("UUID creation", "[Uuid]")
+SCENARIO("UUID creation")

GIVEN("A random UUID ")

I've finally chosen doctest because it promises to be faster: benchmark results.


I've created project using both frameworks you can find them in corresponding branches: doctest branch or catch branch.


I've created an example to illustrate this article:

It is a basic implementation of UUID pseudo-random generator based on mt19937 which is not cryptographically secure.

Project output artifacts

When we install the project using Meson (Ninja), we will get some artifacts generated and copied in our system.

  • Shared library: libuuid.

  • Header library for developers who want to use the shared library: include/Uuid.h.

  • Executable uuidgen (UUID generator).

  • Test executable (not installed). It tests shared library.

For example, if you execute ninja install on Linux, you will get something like:


Project structure (Fork project)


    Root project file configuration. It defines project properties and subdirectories.

        'cpp-meson-example', # project name
        'cpp', # C++ project, e.g: for C project
        version : '1.0.0',
        license : 'MIT',
        default_options : ['cpp_std=c++11']) # compile for C++
    # it will be referred from subdir projects
    inc = include_directories('include')
    # meson will try to find a file inside following directories
  • include

      Build configuration file for include directory.

      # Select header files to be installed
    • Uuid.h

      Header file, it is the library interface definition which will be included from projects using that library

      namespace ids {
      class Uuid {
          // ...
  • src
    • (src)

      It declares 2 output artifacts, library libuuid and executable uuidgen. Executable depends on the libary, it will use the libary to generate UUID.

      libuuid = shared_library(
          'uuid', # library name
          'Uuid.cpp', # source files to be compile
          include_directories : inc, # previously declared include directories in root :code:``
          install : true) # :code:`libuuid` will be part of project installation
      uuidgen = executable(
          'uuidgen', # executable name
          'main.cpp', # source files to compile
          include_directories : inc, # previously declared include directories in root :code:``
          link_with : libuuid, # linking executable with shared previously declared shared library :code:`libuuid`
          install : true) # :code:`uuidgen` executable be part of project installation
    • main.cpp

      Entry point for main executable uuidgen

      #include "Uuid.h"
      #include <iostream>
      int main()
          ids::Uuid uuid;
          std::cout << uuid.to_str() << std::endl;
          return 0;
    • Uuid.cpp

      Implementation of declared class in header file.

      #include "Uuid.h"
      { // ...
  • test
    • (test)

      File to configure tests build process.

      testexe = executable(
          'testexe', # test executable name
          'uuid_test.cpp', # tests source files to be compiled
          include_directories : inc,  # declared include directories in root :code:``
          link_with : libuuid) # link test executable with previously declared shared library :code:`libuuid`
      # test execution
      test('Uuid test', testexe)
      # we can specify other test execution passing arguments or environment variables
      test('Uuid test with args and env', testexe, args : ['arg1', 'arg2'], env : ['FOO=bar'])
    • doctest.h

      doctest library in a single header file. You can try to automate library installation as part of your build process, but I haven't figure out yet a way to do it with Meson. For now I've installed it manually:

      cd test
    • uuid_test.cpp

      Tests implementation.

      #include "doctest.h"
      #include "Uuid.h"
      #include <string>
      constexpr int MAX_ITERS = 100;
          for (int i = 0; i < MAX_ITERS; i++)
              ids::Uuid uuid;
              std::string uuid_str{uuid.to_str()};
              // If assertion fails test execution is stopped
              REQUIRE(uuid_str.size() == 36);
              // If assertion fails test execution continues
              CHECK(uuid.most > 0);
              CHECK(uuid.least > 0);
      // BDD style
      SCENARIO("UUID creation")
          GIVEN("A random UUID ")
              ids::Uuid uuid;
              std::string uuid_str{uuid.to_str()};
              REQUIRE(uuid_str.size() == 36);
              WHEN("get the most and least")
                  THEN("should be more than 0")
                      CHECK(uuid.most > 0);
                      CHECK(uuid.least > 0);


You can find how to build and test the example project at:


Comments powered by Disqus