The Arena
arena_t is the owner of every byte. It holds the reserved memory, carves shards out of it, and knows how much of your budget you have used.
cpp
#include <frameworkcpp/arena/arena.hpp> // arena_t lives here (and the umbrella)The configuration types (arena_config_t, overflow_mode, ...) arrive through the same include; they live on their own in <frameworkcpp/arena/config.hpp>.
Configuring an arena
You build an arena from an arena_config_t. Every field is optional; the defaults are sensible for development, and you can tighten them for real-time:
cpp
arena_config_t config;
config.size = 1024 * 1024; // initial region, in bytes
config.soft_limit = 0; // warn when reserved bytes reach this (0 = off)
config.hard_limit = 0; // hard ceiling on reserved bytes (0 = uncapped)
config.overflow = overflow_mode::throw_error; // throw, or abort() instead
config.on_warning = nullptr; // callback fired once when soft_limit is crossed
config.touch_pages = false; // MAP_POPULATE: commit pages up front
arena_t arena{config};sizeis the first region. The arena may reserve more later, up tohard_limit.- With
hard_limit = 0(the default) the arena grows without a ceiling — handy in development, not what you want for a hard deadline. soft_limitandhard_limitare enforced on reserved bytes (what the mmap segments occupy), not on live objects.- Crossing the hard limit while carving a shard throws
arena_overflow_error(or aborts, ifoverflow == overflow_mode::abort). Crossing the soft limit callson_warningonce and latchesarena.soft_limit_exceeded().
Carving shards
cpp
arena_shard_t create_shard(std::uint64_t tag, std::initializer_list<object_spec_t> specs);
arena_shard_t create_shard(std::uint64_t tag, std::span<const object_spec_t> specs);tagis the shard's identity. Asking for a tag you already created returns the same shard again (idempotent). A tag is just astd::uint64_t— use an enum or named constants so your code reads clearly.specsare the buckets the shard will hold.create_shardreserves the whole slice up front (headers, free-list metadata and all the slots).from(tag)fetches an existing shard by tag, or an empty (falsy) view when it does not exist.
The arena, not the shard
Everything about allocating belongs to the shard page. The arena itself is about ownership and budget:
cpp
arena_t arena;
auto *slot = arena.create_shard(...).allocate<std::uint32_t>();
arena.deallocate(slot); // works even if you lost track of the shardarena.deallocate finds the owning region and bucket from the pointer alone; use it when you only hold a raw pointer. If you still have the shard, its deallocate is faster.
Knowing the budget
cpp
std::size_t shard_count() const noexcept; // how many shards have been carved
std::size_t capacity() const noexcept; // total bytes reserved (all regions)
std::size_t used() const noexcept; // bytes carved so far (high-water mark)
std::size_t live() const noexcept; // slots currently in your hands
std::size_t hard_limit() const noexcept;
std::size_t soft_limit() const noexcept;
bool soft_limit_exceeded() const noexcept;Example
cpp
using namespace frameworkcpp::arena;
// Tag the shard and the buckets so the numbers mean something.
enum class shard_tag : std::uint64_t { workers = 1 };
enum class bucket_tag : std::uint64_t { command = 0, counter = 1 };
arena_config_t config;
config.hard_limit = 4 * 1024 * 1024; // the stock: never reserve past 4 MiB
arena_t arena{config};
auto workers = arena.create_shard(
static_cast<std::uint64_t>(shard_tag::workers),
{
{static_cast<std::uint64_t>(bucket_tag::command),
sizeof(std::uint32_t),
alignof(std::uint32_t),
512},
{static_cast<std::uint64_t>(bucket_tag::counter),
sizeof(std::uint64_t),
alignof(std::uint64_t),
512},
});
std::cout << "reserved: " << arena.capacity() << " bytes\n";