Stack conventions
C++ (CMake)
Modern C++ (17/20/23) libraries and applications built with CMake, tested with CTest and a unit-test framework, under sanitizers.
Modern C++ (17/20/23) libraries and applications built with CMake, tested with CTest and a unit-test framework, under sanitizers.
Used for: cpp-cmake.
Principles
- Follow the C++ Core Guidelines; write modern C++ (C++17 at least, C++20/23 where the toolchain allows) consistent with the project's standard setting.
- Manage every resource with RAII; no raw new/delete, malloc/free or owning raw pointers in application code.
- Apply the rule of zero - let members (std::unique_ptr, std::vector, std::string) manage resources; if you must write one special member, consider all five.
- Express ownership in types - std::unique_ptr for sole ownership, std::shared_ptr only for genuinely shared ownership, references or raw pointers/std::span for non-owning access.
- Make interfaces precise and hard to misuse - strong types, const-correctness, [[nodiscard]], explicit single-argument constructors.
- Prefer the standard library and algorithms (ranges in C++20) over hand-written loops and containers.
- Avoid undefined behaviour; build and test with warnings as errors and with sanitizers (ASan, UBSan, TSan) in CI.
- Use modern target-based CMake - targets with target_* commands and usage requirements, never global include_directories or CMAKE_CXX_FLAGS edits.
- Keep headers minimal and self-contained; include what you use; prefer forward declarations to reduce rebuilds.
Conventions
Accessibility
Concurrency
- Prefer std::jthread and std::stop_token (C++20) or higher-level task abstractions over raw std::thread.
- Protect shared data with std::mutex and std::scoped_lock; never hold a lock while calling unknown code.
- Use std::atomic only for simple flags or counters; document memory ordering when not using the default seq_cst.
- Run ThreadSanitizer on concurrent tests.
Dependencies
- Manage third-party code with vcpkg (manifest mode), Conan or FetchContent/find_package; never copy sources into the tree without a record of version and licence.
- Link dependencies as imported targets (find_package(fmt CONFIG REQUIRED); target_link_libraries(app PRIVATE fmt::fmt)).
Documentation
- Document public headers with Doxygen-style comments covering ownership, lifetime, thread safety and error behaviour.
- Keep the build instructions (cmake --preset ...) in README current.
Errors
- Do not ignore return values of functions that report errors; use [[nodiscard]] to make ignoring visible.
- Throw by value, catch by const reference; do not throw from destructors.
- Check preconditions with assertions or contracts; validate external input at the boundary.
Logging
- Use the project's logging library (spdlog or equivalent) instead of std::cout/printf in library code.
- Never log secrets; keep logging out of hot loops or behind level checks.
Naming
- Follow the repository's naming style (snake_case as in the standard library, or CamelCase types); be consistent, and configure clang-tidy readability-identifier-naming to enforce it.
- Macros in ALL_CAPS and only when unavoidable; prefer constexpr, inline functions and templates.
- Use enum class rather than plain enum.
Performance
- Pass cheap-to-copy types by value, others by const reference; use move semantics for sink parameters.
- Reserve container capacity when the size is known; prefer contiguous containers (std::vector) by default.
- Avoid unnecessary heap allocations and virtual dispatch in hot loops; measure with a profiler and benchmarks (Google Benchmark) before optimising.
- Mark functions constexpr and noexcept where correct.
Security
- Use std::span, std::string_view and bounds-checked access at trust boundaries; never use gets, strcpy, sprintf; prefer std::format (C++20) or fmt.
- Enable compiler hardening (-D_FORTIFY_SOURCE=3 with optimisation, -fstack-protector-strong, _GLIBCXX_ASSERTIONS in debug and test builds).
- Initialise all variables; avoid reinterpret_cast and C-style casts.
- Keep dependencies patched; pin them through the package manager (vcpkg manifest or Conan lockfile) or FetchContent with exact tags/hashes.
- Validate sizes, ranges and encodings of all external input before use; reject rather than truncate.
- Keep secrets out of source and binaries; read them from the environment or a secret store at run time and never log them.
- Never pass external input to system() or popen(); use an argument vector with posix_spawn or a process library (command injection).
Structure
- Public headers under include/
/, sources under src/, tests under tests/; one CMake target per library. - Use #pragma once or include guards consistently; headers compile on their own.
- Use namespaces for all project code; never using namespace in headers.
- Use CMakePresets.json for configure/build/test presets so developers and CI share settings.
- Require a modern CMake minimum (cmake_minimum_required(VERSION 3.20) or newer) and set the standard per target.
Testing
- Use the project's framework (GoogleTest, Catch2 or doctest) and register tests with CTest (gtest_discover_tests, catch_discover_tests).
- Run the test suite under AddressSanitizer and UndefinedBehaviorSanitizer in CI; ThreadSanitizer for concurrent code.
- Fuzz parsers and decoders with libFuzzer where input is untrusted.
- Keep tests deterministic; inject clocks and random sources.
- Unit tests cover classes and functions in isolation; integration tests exercise the built executables or libraries with real files and processes through CTest.
- Name tests after the behaviour (
TEST(Parser, RejectsTruncatedHeader)), one behaviour per test. - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Is every resource owned by an RAII type, with no raw new/delete or owning raw pointers?
- Does each class follow the rule of zero (or define/delete all five special members)?
- Is ownership clear from types (unique_ptr, shared_ptr, references, span, string_view)?
- Are functions const-correct, with [[nodiscard]] on results that must not be ignored and explicit single-argument constructors?
- Is there any undefined behaviour risk (uninitialised variables, dangling references, out-of-bounds, signed overflow)?
- Does the change compile warning-free with the project's warning flags and pass clang-tidy?
- Do tests pass under AddressSanitizer and UndefinedBehaviorSanitizer?
- Is CMake target-based (target_* commands, PUBLIC/PRIVATE usage requirements), with no global flags?
- Are standard algorithms and containers used instead of hand-rolled ones?
- Are concurrent accesses protected and lock scopes minimal?
- Are headers self-contained, free of using namespace, and including what they use?
- Are new dependencies added through the package manager or FetchContent with pinned versions?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
clang-format |
format | clang-format | no |
clang-tidy |
static | clang-tidy | no |
cppcheck |
static | cppcheck | no |
sanitizer-tests |
test | ctest (ASan/UBSan) | no |
osv-scanner |
security | osv-scanner | no |
gitleaks |
security | gitleaks | no |