Skip to main content

loom-cpp

C++ language expertise for idiomatic, production-quality code.

Informations de source

Dépôt
cosmix/loom
Dernière activité de la source
24 septembre 2026 à 17:08
Langue détectée de SKILL.md
anglais
Étoiles
61
Forks
4

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
loom-cpp
description
C++ language expertise for idiomatic, production-quality code.
triggers
["cpp","c++","cmake","ctest","googletest","gtest","catch2","vcpkg","conan","clang-tidy","clang-format","raii","unique_ptr","shared_ptr","move semantics","std::span","string_view","concepts","constexpr","undefined behavior","undefined behaviour","sanitizer","asan","ubsan"]
# C++ Language Expertise ## Overview Idiomatic, production-quality C++20/23 for an engineer who already knows the language. The expensive C++ mistakes compile cleanly: dangling views and references, undefined behaviour the optimizer exploits, resources nobody owns, and CMake targets that leak flags into each other. This skill is decision rules and traps. Check a compiler's support table (cppreference "compiler support") before relying on a C++23 library feature. ## Tooling CMake describes the build, Ninja or Make runs it, CTest runs the tests. Keep the repository's generator, presets and package manager. | Task | Command | | --- | --- | | Configure (out of source) | `cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` | | Configure from a preset | `cmake --preset debug` (`CMakePresets.json`, checked in) | | Build | `cmake --build build -j` | | Test | `ctest --test-dir build --output-on-failure -j 8` (`--test-dir` needs CMake 3.20+) | | List registered tests | `ctest --test-dir build -N` | | Format | `clang-format -i <files>`; CI: `clang-format --dry-run --Werror <files>` | | Lint | `run-clang-tidy -p build` (reads `compile_commands.json` and `.clang-tidy`) | | Add a dependency | `vcpkg add port fmt` (manifest mode, `vcpkg.json`); Conan 2: the recipe plus `conan install . --build=missing` | - vcpkg (toolchain `-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake`) and Conan 2 (`CMakeToolchain` generator) both hand CMake `find_package(... CONFIG REQUIRED)` targets. `FetchContent` downloads sources at configure time and needs network access then, which sandboxed sessions may lack; fetch test frameworks through the package manager or the system. - clang-tidy: enable `bugprone-*`, `modernize-*`, `performance-*`, a pruned `cppcoreguidelines-*`, `misc-const-correctness`; set `WarningsAsErrors: '*'` in `.clang-tidy` so findings fail the gate. It must see the build's flags, which `compile_commands.json` provides. - A pip-installed `cmake` shim earlier on `PATH` can shadow the real CMake suite (its `ctest` fails with `ModuleNotFoundError`). `command -v ctest` shows which binary runs. - Multi-config generators (Visual Studio, Ninja Multi-Config) ignore `CMAKE_BUILD_TYPE`: pass `--config Debug` to `cmake --build` and `-C Debug` to `ctest`. Target-based CMake: every setting belongs to a target, scoped `PRIVATE` (this target's build), `PUBLIC` (this target and its consumers) or `INTERFACE` (consumers only). ```cmake cmake_minimum_required(VERSION 3.25) project(spool LANGUAGES CXX) add_library(spool src/spool_writer.cpp src/journal.cpp) target_include_directories(spool PUBLIC include) target_compile_features(spool PUBLIC cxx_std_20) target_compile_options(spool PRIVATE "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall;-Wextra;-Wpedantic;-Wconversion;-Wshadow;-Werror>" "$<$<CXX_COMPILER_ID:MSVC>:/W4;/WX;/permissive->") include(CTest) # enable_testing() plus the BUILD_TESTING option if(BUILD_TESTING) find_package(GTest CONFIG REQUIRED) add_executable(spool_tests tests/spool_writer_test.cpp) target_link_libraries(spool_tests PRIVATE spool GTest::gtest_main) include(GoogleTest) gtest_discover_tests(spool_tests) # one CTest test per TEST(), named Suite.Name endif() ``` - A generator expression holding several flags is quoted and `;`-separated. Unquoted spaces split it into broken arguments. - `-Werror` goes on your own targets. A global `CMAKE_CXX_FLAGS` edit also applies it to third-party code built in the tree and breaks on the next compiler upgrade. The gate: ```bash clang-format --dry-run --Werror $(git ls-files '*.cpp' '*.hpp') \ && cmake --build build \ && run-clang-tidy -p build -quiet \ && ctest --test-dir build --output-on-failure ``` ## Modern C++ (20/23) - **Concepts** replace SFINAE: constraints read as requirements and produce short errors. ```cpp template <typename T> concept Journal = requires(T j, std::span<const std::byte> bytes) { { j.append(bytes) } -> std::same_as<std::size_t>; j.flush(); }; void drain(Journal auto& journal, std::span<const Record> records); ``` - `auto operator<=>(const T&) const = default;` generates all six comparisons memberwise; a defaulted `operator==` alone gives `==` and `!=`. - `std::format` (C++20) and `std::print`/`std::println` (C++23) check format strings at compile time; they replace `printf` and `iostream` chains. - `std::expected<T, E>` (C++23) carries recoverable errors in the return value; `std::optional`'s `and_then`/`transform`/`or_else` (C++23) chain without nested `if`s. - Ranges and views are lazy and composable: `records | std::views::filter(is_live) | std::views::transform(&Record::id)`. Views refer to their source (see Lifetimes). `filter_view` caches `begin()`, so it cannot be iterated through a `const&`, and modifying an element so it stops satisfying the predicate during iteration is undefined. - Designated initializers `Options{.root = dir, .max_bytes = 1 << 20}` (declaration order only); `consteval` functions and `constexpr` containers move checks to compile time; `std::bit_cast`, `std::to_underlying` (C++23), `std::unreachable` (C++23). - Deducing `this` (C++23 explicit object parameter) removes const/non-const overload pairs and CRTP boilerplate. - Modules build with CMake 3.28+ and Ninja, and `import std;` is still experimental in CMake; headers remain the portable choice for libraries. - Guard optional library features with feature-test macros: `#if __cpp_lib_expected >= 202202L`. ## Ownership and RAII Every resource (memory, file, socket, lock, OS handle) has exactly one owning object whose destructor releases it. | Need | Use | | --- | --- | | Sole owner | a value member, or `std::unique_ptr<T>` from `std::make_unique` | | Shared ownership, lifetime unknown until runtime | `std::shared_ptr<T>` from `std::make_shared` | | Observe a `shared_ptr` without extending it; break cycles | `std::weak_ptr<T>` | | Non-owning access | `T&`, `const T&`, `T*` (nullable), `std::span<T>`, `std::string_view` | | C handle | `std::unique_ptr<Handle, Deleter>` | ```cpp struct FileCloser { void operator()(std::FILE* f) const noexcept { std::fclose(f); } }; using File = std::unique_ptr<std::FILE, FileCloser>; File open_journal(const std::filesystem::path& p) { File f{std::fopen(p.string().c_str(), "ab")}; if (!f) throw std::system_error(errno, std::generic_category(), p.string()); return f; } class SpoolWriter { // rule of zero: every member manages itself public: explicit SpoolWriter(std::filesystem::path root) : root_(std::move(root)) {} void add_sink(std::unique_ptr<Sink> sink) { sinks_.push_back(std::move(sink)); } // ownership moves in private: std::filesystem::path root_; std::vector<std::unique_ptr<Sink>> sinks_; }; ``` - **Rule of zero:** compose from members that manage themselves and declare none of the five special members. A class that manages a resource directly declares all five (destructor, copy and move constructors, copy and move assignment) or `= delete`s the ones it cannot support. - Parameters: read-only `const T&` (by value for cheap types, `std::string_view`, `std::span`); sink parameters by value, then `std::move` into place; in/out `T&`; ownership transfer `std::unique_ptr<T>` by value. A `shared_ptr` by value on every call costs two atomic operations and says nothing about ownership; pass `const T&` unless the callee keeps a copy. - Mark move constructors and move assignment `noexcept`. `std::vector` reallocation copies elements whose move can throw (`std::move_if_noexcept`), so growth quietly becomes deep copies. - A moved-from object is valid but unspecified: assign to it or destroy it, never read it. - `std::move` on a `const` object selects the copy constructor without a diagnostic. `return std::move(local);` blocks copy elision (`-Wpessimizing-move`); return the local by name. - `make_shared` puts the object and control block in one allocation, so a live `weak_ptr` keeps that memory allocated until the last weak reference is gone. `shared_from_this()` in a constructor throws `std::bad_weak_ptr`: no `shared_ptr` owns the object yet. - Destructors are implicitly `noexcept`; an exception escaping one calls `std::terminate`. ## Lifetimes: `string_view`, `span` and Dangling References `std::string_view` and `std::span` are a pointer and a length. They never own and never extend the lifetime of what they point at. All of these compile: ```cpp std::string_view name = std::string("spool-") + id; // BAD: the temporary string dies at the ; std::string_view make_label() { std::string s = build(); return s; // BAD: view into a destroyed local } struct Entry { std::string_view key; }; Entry e{config.get("key")}; // BAD if get() returns std::string by value std::vector<int> v = load(); std::span<int> head = std::span(v).first(3); v.push_back(4); // may reallocate: head now dangles for (auto& r : fetch_batch().records()) { } // BAD before C++23: the batch dies before the loop body ``` - Members and anything that outlives the call hold owning types (`std::string`, `std::vector`). Views are for parameters and locals whose source visibly outlives them. - `string_view::data()` is not null-terminated. Passing it to a C API that expects a C string reads past the end; build a `std::string` first. - Invalidation: a `vector` or `string` insertion that reallocates invalidates every iterator, reference and span into it; `unordered_map` rehashing invalidates iterators while references stay valid; erase during iteration uses the returned iterator or `std::erase_if` (C++20). - A lambda capturing by reference (`[&]`) that outlives its scope (stored callback, thread, coroutine) dangles; capture by value or move owned state in (`[state = std::move(state)]`). - A coroutine parameter taken by reference dangles once the caller's argument is destroyed while the coroutine is suspended. Coroutines take parameters by value. - C++23 (P2718) extends every temporary in a range-`for` initializer to the end of the loop; older standards and compilers still dangle, so bind the owner to a named variable first. - Tooling: GCC 13+ `-Wdangling-reference`; Clang `-Wdangling`, `-Wdangling-gsl` and `[[clang::lifetimebound]]` on accessor parameters; AddressSanitizer catches the rest at runtime (`ASAN_OPTIONS=detect_stack_use_after_return=1`). ## Undefined Behaviour and Sanitizers Undefined behaviour (UB) is a rule violation the compiler assumes never happens. Optimizers delete checks that could only fail through UB (`if (x + 1 < x)` for signed `x` folds to `false`), so UB surfaces far from its cause and often only in release builds. Common sources: - signed integer overflow; shifting by a negative count or by the type's width or more; `INT_MIN / -1` - out-of-bounds access (`operator[]` does not check), use after free, double free, dereferencing null or an end iterator - reading an uninitialized variable; a non-`void` function falling off its end - data races: two threads, at least one writing, no synchronization - type punning through a pointer cast (strict aliasing); use `std::memcpy` or `std::bit_cast` - modifying a `const` object or a string literal; misaligned access through a cast pointer - a loop with no observable side effects that never terminates Sanitizers instrument the build and report UB at runtime. Run the test suite under them in CI: | Sanitizer | Flags | Finds | Notes | | --- | --- | --- | --- | | AddressSanitizer | `-fsanitize=address -fno-omit-frame-pointer` | out-of-bounds, use after free, leaks (LeakSanitizer on Linux) | about 2x slower; combine with UBSan | | UndefinedBehaviorSanitizer | `-fsanitize=undefined -fno-sanitize-recover=all` | overflow, bad shifts, null, misalignment, invalid `bool`/enum loads | without `-fno-sanitize-recover` it prints and continues, and the test still passes | | ThreadSanitizer | `-fsanitize=thread` | data races, lock-order inversions | separate build; cannot combine with ASan | | MemorySanitizer | `-fsanitize=memory` (Clang only) | uninitialized reads | every linked library, the standard library included, must be instrumented | ```cmake option(SPOOL_SANITIZE "Build with AddressSanitizer and UBSan" OFF) if(SPOOL_SANITIZE) # before any add_library/add_executable: only later targets pick it up add_compile_options(-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer) add_link_options(-fsanitize=address,undefined) endif() ``` - `-D_GLIBCXX_ASSERTIONS` (libstdc++) or `-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST` (libc++ 18+) adds bounds checks to `operator[]`, `front()` and similar at small cost, cheap enough for many production builds. GCC 14's `-fhardened` bundles `_GLIBCXX_ASSERTIONS` with `_FORTIFY_SOURCE=3`, the stack protector and PIE. - Fuzz parsers and decoders with libFuzzer (`-fsanitize=fuzzer,address`). - Optimization level changes which UB shows. Run sanitizer builds at `-O1` or higher as well as at `-O0`. ## Concurrency - `std::jthread` (C++20) joins in its destructor and carries a `std::stop_token`. Destroying a joinable `std::thread` calls `std::terminate`. - `std::scoped_lock` locks several mutexes with deadlock-free ordering; `std::unique_lock` when a `condition_variable` needs one. Wait with a predicate, which handles spurious wakeups: `cv.wait(lock, [&] { return !queue.empty(); });`. - `std::atomic<T>` defaults to `memory_order_seq_cst`. A weaker order (`acquire`, `release`, `relaxed`) needs a written ordering argument, checked under ThreadSanitizer.
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub