Skip to main content

loom-cpp

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

Quellinformationen

Repository
cosmix/loom
Letzte Quellaktivität
24. September 2026 um 17:08
Erkannte Sprache von SKILL.md
Englisch
Sterne
61
Forks
4

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen