#pragma once #include #include #include namespace prism { // An instrumented interface, not an owner of upstream. Declare upstream first, // this resource second, and its borrowing containers last (reverse destruction). // This deliberately single-threaded teaching resource needs external locking // if shared. Counters describe live requested bytes, not arena physical capacity. class CountingResource final : public std::pmr::memory_resource { public: explicit CountingResource(std::pmr::memory_resource& upstream) noexcept : upstream_{upstream} {} ~CountingResource() override = default; CountingResource(const CountingResource&) = delete; CountingResource& operator=(const CountingResource&) = delete; // Borrowers retain this resource's address and equality is identity-based. CountingResource(CountingResource&&) = delete; CountingResource& operator=(CountingResource&&) = delete; [[nodiscard]] std::size_t allocations() const noexcept { return allocations_; } [[nodiscard]] std::size_t live_bytes() const noexcept { return live_; } [[nodiscard]] std::size_t peak_bytes() const noexcept { return peak_; } private: void* do_allocate(std::size_t bytes, std::size_t alignment) override; void do_deallocate(void* pointer, std::size_t bytes, std::size_t alignment) override; bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } std::pmr::memory_resource& upstream_; std::size_t allocations_{}, live_{}, peak_{}; }; struct MemoryReport { std::string promoted_title; // Independent ordinary owner survives arena release. std::size_t allocations{}, peak_bytes{}, live_after_scope{}; bool bounded_failure{}; }; [[nodiscard]] MemoryReport run_memory_lab(); } // namespace prism