Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Jungle 引擎

Jungle 是一个基于 C++26 反射的实验性游戏引擎,探索现代 C++ 标准在游戏开发中的应用。

设计理念

  • 零开销抽象:编译期反射和模板元编程替代运行时类型系统,不依赖 RTTI 和异常
  • 类型安全:通过 concept 和强类型别名在编译期捕获错误
  • 模块化:基础库、ECS 核心、渲染、网络等按依赖分层

编译要求

  • 暂时仅支持 GCC
  • C++26 标准
  • 反射(-freflection)
  • 禁用 RTTI(-fno-rtti)
  • 禁用异常(-fno-exceptions)

Jungle 基础库

jungle-base 是 Jungle 引擎的基础设施层,提供类型系统、序列化框架、反射工具、测试框架等底层能力。所有上层模块(jungle-core、jungle-server 等)均依赖本库。

模块结构

include/jungle/
├── concepts.h        # 概念约束(Debug、is_enum 等)
├── debug.h           # 基于反射的通用 debug 输出
├── meta.h            # 反射元编程工具
├── panic.h           # 不可恢复错误处理
├── preusing.h        # 常用类型别名集中引入
├── container/        # 容器
│   ├── hash_map.h    # 自定义哈希映射
│   └── mpsc.h        # MPSC 无锁有界队列
├── serde/            # 序列化反序列化框架
│   ├── serde.h       # 概念定义与注解
│   ├── serialize.h   # SerializeTarget 基类与 serialize() 自由函数
│   └── deserialize.h # DeserializeSource 基类与 deserialize() 自由函数
├── test/             # 测试框架
│   └── test.h        # JUNGLE_SYNC_TEST 宏与断言
├── types/            # 基础类型
│   ├── int.h         # 固定宽度整数别名(u8, i32, usize 等)
│   └── uchar.h       # Unicode 字符与字符串(uchar, ustr)
└── util/             # 工具
    ├── murmur.h      # MurmurHash 哈希
    ├── parse.h       # Base64 编码视图
    ├── type_id.h     # 编译期类型 ID
    └── types.h       # 类型特征工具

编译要求

  • C++23 标准(启用了 -std=c++23)
  • 启用反射扩展(-freflection)
  • 禁用 RTTI 和异常(-fno-rtti -fno-exceptions)

关键组件

类型系统

types/int.h 定义了固定宽度的整数别名,与 std 对应:

别名对应类型
u8 ~ u64std::uint8_t ~ std::uint64_t
i8 ~ i64std::int8_t ~ std::int64_t
usizestd::size_t
isizestd::ptrdiff_t

types/uchar.h 提供了 uchar(Unicode 码点)和 ustr(UTF-8 字符串),支持 std::format。

反射工具 (meta.h)

基于 C++26 反射提供编译期类型内省:

  • has_annotation() — 检查类型/成员是否有某注解
  • has_template_annotation() — 检查类型/成员是否有某模板注解的实例
  • nth_template_annotation_argument_of() — 获取模板注解的第 N 个参数
  • is_specialization_of_template() — 判断是否为某模板的实例
  • nonstatic_data_members_with_annotation() — 获取带某注解的所有非静态数据成员

序列化框架 (serde/)

基于反射的类型安全序列化/反序列化框架。详见 序列化反序列化框架。

核心特性:

  • 零宏、零外部代码生成——完全依赖 C++26 编译期反射
  • CRTP 设计:实现 SerializeTarget 或 DeserializeSource 即可接入
  • 通过注解控制字段参与策略([[=customized]] / [[=field]])
  • 支持字段级定制器([[=customize<C>]])
  • 递归处理嵌套 struct、容器、std::optional

测试框架 (test/)

轻量级同步测试框架:

JUNGLE_SYNC_TEST(my_test) {
    JUNGLE_SYNC_ASSERT(1 + 1 == 2, "basic arithmetic should work");
    JUNGLE_SYNC_SUCCESS();
}

通过 ctest 集成运行。

错误处理 (panic.h)

禁用异常的环境下使用 panic() 处理不可恢复错误,支持格式化消息。

调试输出 (debug.h)

基于反射的通用 debug() 函数,支持的类别覆盖基础类型、枚举、范围、class/struct(含私有成员)。

Serde 序列化反序列化框架

Jungle 的 serde 是一个基于 C++26 反射的序列化/反序列化框架。它不依赖宏或外部代码生成——所有类型信息在编译期通过反射获取。

接口、行为与约束

核心概念

框架将“如何表示数据“与“数据本身是什么“解耦:

  • Target(序列化目标):接收类型安全的数据写入调用,产生某种输出格式
  • Source(反序列化源):从某种输入格式读取数据,写入已有变量
  • Customizer(定制器):在序列化/反序列化过程中对特定字段的值做变换

类型分派顺序

序列化和反序列化的自由函数按以下顺序对类型做编译期分派:

  1. bool
  2. std::integral(整数类型)
  3. std::floating_point(浮点类型)
  4. enum
  5. std::optional<T>
  6. std::ranges::range(容器/范围类型)
  7. class / struct

如果一个类型不匹配以上任何类别,编译期 static_assert 报错。


序列化

序列化自由函数

// 写入已有 target
template<SerializeTargetImpl Target, typename T>
void serialize(const T &value, Target &target);

// 创建 target 并返回结果
template<SerializeTargetImpl Target, typename T>
typename Target::target_type serialize(T &&value);

serialize(value, target) 将 value 写入 target。便捷重载 serialize<Target>(value) 默认构造 Target、调用上述函数、并通过 target.deliver_result() 返回最终产物。

SerializeTarget 基类

所有 Target 必须继承 SerializeTarget<Target>(CRTP),满足 SerializeTargetImpl concept,且可默认构造:

  • 定义 target_type(最终产物的类型)
  • 实现 deliver_result() -> target_type
  • 实现 spawn_subtarget() -> Target(创建子 target,用于嵌套结构)
  • 实现各类型的 serialize_* 方法(基类通过 CRTP 调用)

基类 SerializeTarget 提供的模板方法及其 requires 约束:

基类方法调用的派生类方法
serialize_bool(const bool &)serialize_bool(const bool &) -> void
serialize_integral(const I &)serialize_integral(const I &) -> void
serialize_floating_point(const F &)serialize_floating_point(const F &) -> void
serialize_enum(const T &)serialize_enum(const T &) -> void
serialize_optional(const optional<T> &)serialize_optional_nonnull() -> void、serialize_optional_nullopt() -> void
serialize_range(const R &)serialize_range_head() -> void、serialize_range_element_end() -> void、serialize_range_tail(usize) -> void
serialize_class_object(const T &)serialize_class_head(string_view) -> void、serialize_class_field(string_view) -> void、serialize_class_field_end() -> void、serialize_class_tail(string_view) -> void

class 的序列化流程

对于 class 类型,框架使用反射遍历所有非静态数据成员:

  • 不含 [[=customized]] 注解:所有非静态数据成员均被序列化
  • 含 [[=customized]] 注解:仅带 [[=field]] 标记的成员被序列化
  • 对于带 [[=customize<C>]] 注解的成员,由定制器 C 的 serialize 方法接管该字段的输出

反射使用 std::meta::access_context::unchecked() 访问私有成员。


反序列化

反序列化自由函数

// 从 payload 构造新值并返回(T 需 default_constructible)
// 成功时返回值,失败时返回 unexpected(Source::error_type)
template<typename T, DeserializeSourceImpl Source>
  requires std::is_default_constructible_v<T>
std::expected<T, typename Source::error_type>
deserialize(const typename Source::source_type &source_payload);

// 从 payload 写入已有变量
template<typename T, DeserializeSourceImpl Source>
[[nodiscard]] std::expected<void, typename Source::error_type>
deserialize(const typename Source::source_type &source_payload, T &value);

// 直接操作 source,写入已有变量
template<typename T, DeserializeSourceImpl Source>
[[nodiscard]] std::expected<void, typename Source::error_type>
deserialize(Source &source, T &value);

所有反序列化操作都通过 std::expected 表示成功/失败:写入已有变量的重载返回 expected<void, Source::error_type>,构造新值的重载返回 expected<T, Source::error_type>。错误从 deserialize_* 层层向上传递到这些自由函数。调用方负责检查返回值。

DeserializeSource 基类

所有 Source 必须继承 DeserializeSource<Source>(CRTP),满足 DeserializeSourceImpl concept,且可默认构造:

  • 定义 source_type(原始输入类型)
  • 定义 error_type(反序列化失败时的错误类型)
  • 实现 provide_source(const source_type &) -> void
  • 实现 spawn_subsource() -> Source
  • 实现各类型的 deserialize_* 方法,所有方法返回 std::expected<void, error_type>

基类 DeserializeSource 提供的模板方法及其 requires 约束(下表中 E 表示 Source::error_type):

基类方法调用的派生类方法
deserialize_bool(bool &) -> expected<void, E>deserialize_bool(bool &) -> expected<void, E>
deserialize_integral(I &) -> expected<void, E>deserialize_integral(I &) -> expected<void, E>
deserialize_floating_point(F &) -> expected<void, E>deserialize_floating_point(F &) -> expected<void, E>
deserialize_enum(T &) -> expected<void, E>deserialize_enum(T &) -> expected<void, E>
deserialize_optional(OptionalT &) -> expected<void, E>deserialize_optional_nonnull() -> expected<void, E>、deserialize_optional_nullopt() -> expected<void, E>
deserialize_range(R &) -> expected<void, E>deserialize_range_head() -> expected<void, E>、deserialize_range_has_element() -> expected<void, E>、deserialize_range_element_end() -> expected<void, E>、deserialize_range_tail() -> expected<void, E>
deserialize_class_object(T &) -> expected<void, E>deserialize_class_head() -> expected<void, E>、deserialize_class_field() -> expected<void, E>、deserialize_class_field_end() -> expected<void, E>、deserialize_class_tail() -> expected<void, E>

与序列化不同,反序列化的结构方法(head、field、element_end 等)全部返回 expected<void, E> 而非 void。当解析到非法格式时可返回 unexpected 使上层回退或报错,而不是通过 panic 终止进程。

deserialize_optional_nonnull / deserialize_optional_nullopt 以及 deserialize_range_has_element 用成功表示“匹配到该情况 / 仍有元素”,用 unexpected 表示“不是该情况 / 没有更多元素”。基类在 optional 两条路径都失败时把后者的错误向上传递;range 则在 has_element 失败时结束循环并继续解析 tail。

range 的反序列化流程

if (auto r = deserialize_range_head(); !r) return r;   → 失败则终止并传递错误
while deserialize_range_has_element() 成功:
    spawn_subsource() → 创建子 source
    if (auto r = deserialize(subsource, elem); !r) return r;  → 递归反序列化元素
    if (auto r = deserialize_range_element_end(); !r) return r;
if (auto r = deserialize_range_tail(); !r) return r;

范围类型必须支持 insert(value.end(), value_type);框架按输入顺序将每个已反序列化的元素插入目标范围。

class 的反序列化流程

与序列化对称,但 deserialize_class_head() 和 deserialize_class_field() 仅返回 expected<void, E>(成功与否),不返回解析到的类型名/字段名——这些字符串在反序列化中仅作验证用途,由基类的控制流统一处理失败返回。

字段定制器直接接收成员引用和子 source,并负责原地写入成员;其返回的 expected 会被基类检查并向上传递:

if (auto r = customizer_instance.deserialize(value.[:m:], subsource); !r) {
    return r;
}

注解

注解用途
[[=customized]]标注 class 为定制序列化——只有 [[=field]] 成员参与
[[=field]]标记某一成员参与序列化(仅在 [[=customized]] class 中有意义)
[[=customize<C>]]指定字段级定制器 C,该字段的序列化/反序列化由 C 接管

注解可以组合使用:

struct [[= customized]] S {
  [[= field]] int normal = 1;
  [[= field]] [[= customize<PlusThousand>]] int boosted = 100;
  int skipped = 999;   // 无 [[=field]],不参与序列化
};

Customizer 概念

template<template<typename> typename Custr>
concept Customizer =
  std::is_default_constructible_v<Custr<int>> &&
  requires(Custr<int> c, int value, detail::TraitTargetSource &target) {
    { c.serialize(value, target) }      -> std::same_as<void>;
    { c.deserialize(value, target) }
      -> std::same_as<std::expected<void, typename detail::TraitTargetSource::error_type>>;
  };

定制器是一个单参数模板:template<typename T> struct MyCustomizer { ... }。框架会为每个使用该定制器的字段实例化 MyCustomizer<字段类型>。

  • serialize(const T &value, auto &target):将 value 经过变换后写入 target,返回 void
  • deserialize(T &value, auto &source):从 source 读取原始值,并直接写入变换后的 value,返回 std::expected<void, typename Source::error_type>

deserialize 必须把内部 deserialize_* 调用的错误原样向上返回;框架会检查该返回值并继续向上传递。


实现 Source/Target 与 Customizer

实现 TextTarget

以下是一个将数据序列化为可读文本格式的 Target 实现(位于单元测试目录,仅作参考):

class TextTarget : public SerializeTarget<TextTarget> {
public:
    using target_type = ustr;

    TextTarget() = default;
    target_type deliver_result() { return std::move(m_result); }

    TextTarget spawn_subtarget() { return TextTarget{m_result}; }

    void serialize_bool(const bool &value) {
        m_result.append(value ? "true" : "false");
    }

    template<std::integral I>
    void serialize_integral(const I &value) {
        m_result.append(ustr::format("{}", value));
    }

    template<std::floating_point F>
    void serialize_floating_point(const F &value) {
        m_result.append(ustr::format("{}", value));
    }

    template<concepts::is_enum E>
    void serialize_enum(const E &value) {
        m_result.append(debug(value));
    }

    void serialize_optional_nonnull()  { m_result.append("optional##"); }
    void serialize_optional_nullopt()  { m_result.append("optional##nullopt"); }
    void serialize_range_head()        { m_result.append("["); }
    void serialize_range_element_end() { m_result.append(","); }
    void serialize_range_tail(usize)   { m_result.append("]"); }
    void serialize_class_head(std::string_view ident) {
        m_result.append(ustr::format("{}{{", ident));
    }
    void serialize_class_field(std::string_view ident) {
        m_result.append(ustr::format("{}:", ident));
    }
    void serialize_class_field_end()   { m_result.append(","); }
    void serialize_class_tail(std::string_view) { m_result.append("}"); }

private:
    TextTarget(ustr &external) : m_storage{std::nullopt}, m_result{external} {}
    std::optional<ustr> m_storage{ustr{}};
    ustr &m_result{*m_storage};
};

static_assert(SerializeTargetImpl<TextTarget>);

关键设计点:

  • spawn_subtarget() 通过引用共享底层缓冲区,子 target 的输出追加到同一字符串末尾
  • 使用 ustr::format 格式化数值,debug() 格式化枚举
  • 各 serialize_* 方法的输出格式需与对应 TextSource 的 deserialize_* 解析逻辑保持一致

实现 TextSource

class TextSource : public DeserializeSource<TextSource> {
public:
    using source_type = ustr;
    enum class error_type { mismatch };

    TextSource() = default;
    void provide_source(const source_type &source) {
        m_source = source.view();
        *m_cursor = 0;
    }
    TextSource spawn_subsource() { return TextSource{m_source, m_cursor}; }

    std::expected<void, error_type> deserialize_bool(bool &value) { /* 解析 "true"/"false" */ }
    template<std::integral I>
    std::expected<void, error_type> deserialize_integral(I &value) { /* from_chars 解析 */ }
    template<std::floating_point F>
    std::expected<void, error_type> deserialize_floating_point(F &value) { /* from_chars 解析 */ }
    template<concepts::is_enum E>
    std::expected<void, error_type> deserialize_enum(E &value) { /* 按名字匹配枚举项 */ }

    std::expected<void, error_type> deserialize_optional_nonnull() { /* 消费 "optional##",若非 "nullopt" 则成功 */ }
    std::expected<void, error_type> deserialize_optional_nullopt() { /* 消费 "nullopt" */ }
    std::expected<void, error_type> deserialize_range_head()        { /* 消费 "[" */ }
    std::expected<void, error_type> deserialize_range_has_element() { /* 检查下个字符是否为 "]" */ }
    std::expected<void, error_type> deserialize_range_element_end() { /* 消费 "," */ }
    std::expected<void, error_type> deserialize_range_tail()        { /* 消费 "]" */ }
    std::expected<void, error_type> deserialize_class_head()        { /* 消费 "TypeName{" */ }
    std::expected<void, error_type> deserialize_class_field()       { /* 消费 "fieldName:" */ }
    std::expected<void, error_type> deserialize_class_field_end()   { /* 消费 "," */ }
    std::expected<void, error_type> deserialize_class_tail()        { /* 消费 "}" */ }

private:
    TextSource(std::string_view source, usize *cursor)
        : m_source{source}, m_cursor{cursor} {}
    std::string_view m_source;
    usize m_owned_cursor{0};
    usize *m_cursor{&m_owned_cursor};
};

static_assert(DeserializeSourceImpl<TextSource>);

关键设计点:

  • 所有方法返回 std::expected<void, error_type>:解析成功返回 {},格式不匹配返回 unexpected(不 panic)
  • spawn_subsource() 通过共享游标实现:子 source 读取时自动推进父 source 的位置
  • deserialize_optional_nonnull() 消费 "optional##" 前缀后仅 peek(不消费)"nullopt";若确实为 nullopt 则返回 unexpected,由 deserialize_optional_nullopt() 消费
  • deserialize_class_head() 和 deserialize_class_field() 只返回 expected<void, error_type>,不返回解析到的类型名/字段名——这些值在反序列化路径中未被使用

实现 Customizer

以下定制器在序列化时对值 +1000,反序列化时 -1000:

template<typename T>
struct PlusThousand {
    void serialize(const T &value, auto &target) const {
        target.serialize_integral(value + 1000);
    }
    auto deserialize(T &value, auto &source) const
        -> std::expected<void, typename std::remove_cvref_t<decltype(source)>::error_type> {
        if (auto r = source.template deserialize_integral<T>(value); !r) {
            return r;
        }
        value -= 1000;
        return {};
    }
};

static_assert(Customizer<PlusThousand>);

使用方法:

struct S {
    [[= customize<PlusThousand>]] int score = 42;
};

定制器必须满足:

  • 是一个单参数模板(template<typename> struct)
  • serialize 接收 const T & 和 target 引用,返回 void
  • deserialize 接收 T & 和 source 引用,返回 std::expected<void, typename Source::error_type>,并原地更新该值
  • deserialize 内部调用 source 的值写入方法(如 deserialize_integral)获取原始值,并把错误原样返回

使用示例

// 序列化
ustr text = serialize<TextTarget>(my_object);

// 反序列化(构造新值,返回 expected<T, error_type>)
auto result = deserialize<MyStruct, TextSource>(text);
if (result.has_value()) {
    // 使用 *result
}

// 反序列化(写入已有变量,返回 expected<void, error_type>)
MyStruct obj;
auto ok = deserialize<MyStruct, TextSource>(text, obj);

// 反序列化(直接操作 source)
TextSource src;
src.provide_source(text);
auto ok = deserialize(src, obj);

type_mutate 运行时类型可变基类

jungle::util::type_mutate<T> 是一个 CRTP 基类,为派生类提供编译期安全的运行时类型查询与向下转型能力。它不依赖 RTTI,而是基于 type_id 做精确的类型标识比对。

设计意图

在 ECS 等场景中,Component<> 和 Manager<> 需要以无类型基类引用的方式在系统中流转,同时又要在运行时安全地恢复为具体派生类型。type_mutate 通过在每个实例中存储一个 type_id 并提供带编译期约束的转型方法,解决了这一需求。

核心机制

每个派生类必须定义一个静态成员模板 static_mutatable,声明哪些类型是该派生类的合法变体:

template<>
class Component<> : public util::type_mutate<Component<>> {
public:
    template<typename C>
    static constexpr bool static_mutatable = ComponentImpl<C>;
    // ...
};

type_mutate 内部通过 requires 子句引用此约束:

template<typename U>
    requires static_mutatable<U>
constexpr bool is() const { ... }

这意味着编译期即可拒绝对非法类型的查询——如果你试图 base.is<WrongType>(),编译器会直接报错,而非等到运行时返回 false。

构造

protected:
    constexpr type_mutate(type_id type)
            : m_type{type} {}

构造函数为 protected,仅派生类可调用。派生类在构造时必须传入正确的 type_id(通常为 type_id::of<DerivedType>())。

API

方法说明
is<U>() const -> bool当前实例是否为 U 类型。编译期要求 U 满足 static_mutatable
is(type_id) const -> bool当前实例的类型 ID 是否匹配指定 type_id(无编译期类型约束)
as<U>() -> U &向下转型为 U&。JUNGLE_ASSERT(is<U>()),仅 Debug 模式检查
as<U>() const -> const U &const 重载版
try_as<U>() -> U *安全向下转型,类型不匹配返回 nullptr
try_as<U>() const -> const U *const 重载版
type() const -> type_id返回当前实例存储的类型 ID

is

// 编译期类型检查版——U 必须满足 static_mutatable
template<typename U>
    requires static_mutatable<U>
constexpr bool is() const;

// 无约束版——接受任意 type_id,适合数据驱动的类型比对
constexpr bool is(type_id type) const;

前者用于类型安全的编译期分派,后者用于需要运行时 type_id 比对的场景(如从配置或网络读取类型标识)。

as

template<typename U>
    requires static_mutatable<U>
constexpr U &as();

as<U>() 执行向下转型。Debug 模式下通过 JUNGLE_ASSERT(is<U>()) 校验类型是否匹配,不匹配时触发 panic();Release 模式下该断言会被完全移除,不做任何检查。

典型用法:

void process(Component<> &base) {
    if (base.is<HealthComponent>()) {
        auto &health = base.as<HealthComponent>();
        health.hp -= 10;
    }
}

try_as

template<typename U>
    requires static_mutatable<U>
constexpr U *try_as();

try_as<U>() 是安全的非 panic 版本——类型不匹配时返回 nullptr。适合无法预先保证类型匹配、也不想触发断言 panic 的场景:

void maybe_process(Component<> &base) {
    if (auto *health = base.try_as<HealthComponent>()) {
        health->hp -= 10;
    }
    // 或:
    // auto *health = base.try_as<HealthComponent>();
    // if (health) { ... }
}

const 重载行为一致,返回 const U * / const U &。

type

type_id type() const;

直接返回存储的类型 ID,主要用于日志、调试或类型路由。

与 Component<> 和 Manager<> 的关系

type_mutate 在 Jungle ECS 中有两个主要使用者:

  • Component<>:所有具体 Component 继承自此,static_mutatable 约束为满足 ComponentImpl concept 的类型
  • Manager<>:所有具体 Manager 继承自此,static_mutatable 约束为满足 ManagerImpl concept 的类型

这使得系统可以用 Component<> & 或 Manager<> & 统一操作不同类型,并在需要具体类型时安全恢复。

类型安全层级

层级机制失败行为
编译期static_mutatable 约束编译错误
DebugJUNGLE_ASSERT(is<U>())panic() 中止进程
Release无检查(断言被移除)未定义行为
运行时安全try_as<U>() 返回 nullptr调用方自行检查空指针

MPSC 无锁有界队列

jungle::container::mpsc<T> 是一个多生产者单消费者(MPSC)无锁有界队列。它基于环形缓冲区实现,使用原子操作保证无数据竞争的并发安全。

特性摘要

  • 无锁设计:发送和接收均无互斥锁,仅依赖 std::atomic 操作
  • 多生产者安全:sender 可自由拷贝,多线程并发发送安全
  • 单消费者:receiver 不可拷贝,同一时刻只能有一个消费者
  • 有界容量:创建时指定最小容量,内部向上取整为 2 的幂
  • 类型兼容:支持可移动、仅拷贝、平凡类型(通过 try_move_t 自动分派)

创建队列

通过静态工厂方法 queue() 创建 sender / receiver 对:

#include "jungle/container/mpsc.h"

using jungle::container::mpsc;

// 默认最小容量 1023
auto [sender, receiver] = mpsc<int>::queue();

// 指定最小容量
auto [s, r] = mpsc<std::string>::queue(8);

参数 size 指定最小容量保证。

队列存储由 std::shared_ptr 管理,所有 sender 和 receiver 共享同一底层缓冲区。

发送

sender 可拷贝、可移动。多个 sender 副本可并发调用 send():

auto [s1, receiver] = mpsc<int>::queue();
auto s2 = s1;       // sender 可拷贝,共享同一队列
auto s3 = s1;

s1.send(10);        // 从线程 A 发送
s2.send(20);        // 从线程 B 发送
s3.send(30);        // 从线程 C 发送

send 签名

[[nodiscard]] bool send(try_move_t<T> value);
  • 参数类型 try_move_t<T> 自动选择最优传递方式:
    • 可移动类型 → T&&(移动语义)
    • 仅拷贝类型 → const T&(拷贝语义)
    • 基础类型(int 等) → T(按值传递)
  • 返回 true 表示发送成功,false 表示队列已满
if (!sender.send(data)) {
    // 队列已满,可选择重试或丢弃
}

接收

receiver 不可拷贝,仅可移动。同一时刻只应有一个线程调用 recv():

auto val = receiver.recv();
if (val.has_value()) {
    // 处理 *val
}

recv 签名

[[nodiscard]] std::optional<T> recv();
  • 返回 std::optional<T>:
    • 有值:成功取出队首元素
    • std::nullopt:队列为空

元素以 FIFO(先进先出)顺序被接收。

容量语义

auto [s, r] = mpsc<int>::queue(4);

// 保证至少发送 4 个元素成功
for (int i = 0; i < 4; ++i) {
    s.send(i);  // 全部成功
}

// 不应假设第 5 个必定失败
// 实际容量可能更大

如需确切知道队列何时满,循环发送直到 send() 返回 false。

线程安全

操作安全性
send多线程安全(多生产者并发)
recv单线程(同一 receiver 不可并发)
send + recv安全(生产者与消费者可并发)

使用示例

基本发送与接收

auto [sender, receiver] = mpsc<int>::queue();

sender.send(42);
auto val = receiver.recv();
// *val == 42

多生产者

auto [s1, receiver] = mpsc<int>::queue();
auto s2 = s1;
auto s3 = s1;

s1.send(10);
s2.send(20);
s3.send(30);

// 按发送顺序接收:10, 20, 30
receiver.recv();  // 10
receiver.recv();  // 20
receiver.recv();  // 30

仅移动类型

auto [sender, receiver] = mpsc<std::unique_ptr<int>>::queue();

sender.send(std::make_unique<int>(42));
auto ptr = receiver.recv();
// *ptr == 42

排空队列

while (auto val = receiver.recv()) {
    process(*val);
}

注意事项

  • receiver 必须从 queue() 返回的元组中获取;不存在独立的 receiver 默认构造
  • 发送者全部销毁后,队列内存由最后一个持有共享状态的 receiver 或 sender 负责释放
  • 本队列为有界队列,不适用于生产者速率持续大于消费者的场景