Skip to content

Latest commit

 

History

History
65 lines (44 loc) · 4.36 KB

coding_guidelines.md

File metadata and controls

65 lines (44 loc) · 4.36 KB

Guidelines for writing C++ code for snitch

General

Unless otherwise stated, follow the C++ Core Guidelines. Below are exceptions to these guidelines, or more opinionated choices.

noexcept

Follow the Lakos Rule (with some tweaks):

  • Functions with a wide contract (i.e., with no precondition) should be marked as unconditionally noexcept.
  • Functions with a narrow contract (i.e., with preconditions), should not be marked as noexcept.
  • If a template function is conditionally-wide, (e.g., its purpose is only to be a wrapper or adapter over another function), then it may be marked conditionally noexcept.

In particular:

  • Do not mark a function noexcept just because it happens not to throw. The decision should be based on the interface of the function (which includes its pre-condition), and not its implementation.

Rationale:

  • Easy transition to using contracts when they come to C++.
  • Enable testing for pre-condition violations by conditionally throwing.

Heap allocations

snitch code must not directly or indirectly allocate heap (or "free store") memory while running tests. This means that a number of common C++ STL classes cannot be used (at least not with the default allocator):

  • std::string: use std::string_view (for constant strings) or snitch::small_string (for variable strings) instead.
  • std::vector, std::map, std::set, and their variants: use std::array (for fixed size arrays) or snitch::small_vector (for variable size arrays) instead.
  • std::function: use snitch::function_ref instead.
  • std::unique_ptr, std::shared_ptr: use values on the stack, and raw pointers for non-owning references.

Unfortunately, the standard does not generally specify if a function or class allocates heap memory or not. We can make reasonable guesses for simple cases; in particular the following are fine to use:

Any type or function not listed above should be assumed to use heap memory unless demonstrated otherwise. One way to monitor this on Linux is to use valgrind/massif.

Note: If necessary, it is acceptable to allocate/de-allocate heap memory when a test is not running, i.e., either at the start or end of the program, or (if single-threaded) in between test cases. For example, as of writing this, with GCC 10 on Linux and the default reporter, snitch performs heap allocations on two occasions:

  • several allocations at program startup, generated by libstdc++ initialisation.
  • one at the first console output, generated by glibc for its internal I/O buffer.

Heavy headers and compilation time

One of the advantages of snitch over competing testing framework is fast compilation of tests. To preserve this advantage, "heavy" STL headers should not be included in snitch headers unless absolutely necessary. However, they can be included in the snitch implementation *.cpp files.

Therefore:

  • Place as much code as possible in the *.cpp files rather than in headers.
  • When not possible (templates, constexpr, etc.), consider if you can use a short and clear handwritten alternative instead. For example, std::max(a, b) requires <algorithm>, but can also be written as a > b ? a : b. Some of the simplest algorithms in <algorithm>, like std::copy, can also be written with an explicit loop.
  • Finally, consider if you really need the full feature from the STL, or just a small subset. For example, if you need a metaprogramming type list and don't need to instanciate the types, don't use std::tuple<...>: use a custom template<typename ... Args> struct type_list {} instead.