Skip to main content

loom-cpp

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

ソース情報

リポジトリ
cosmix/loom
ソースの最終更新活動
2026年9月24日 17:08
検出された SKILL.md の言語
英語
スター
61
フォーク
4

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
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.
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る