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 ~ u64 | std::uint8_t ~ std::uint64_t |
i8 ~ i64 | std::int8_t ~ std::int64_t |
usize | std::size_t |
isize | std::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(定制器):在序列化/反序列化过程中对特定字段的值做变换
类型分派顺序
序列化和反序列化的自由函数按以下顺序对类型做编译期分派:
boolstd::integral(整数类型)std::floating_point(浮点类型)enumstd::optional<T>std::ranges::range(容器/范围类型)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,返回voiddeserialize(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 引用,返回voiddeserialize接收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约束为满足ComponentImplconcept 的类型Manager<>:所有具体 Manager 继承自此,static_mutatable约束为满足ManagerImplconcept 的类型
这使得系统可以用 Component<> & 或 Manager<> & 统一操作不同类型,并在需要具体类型时安全恢复。
类型安全层级
| 层级 | 机制 | 失败行为 |
|---|---|---|
| 编译期 | static_mutatable 约束 | 编译错误 |
| Debug | JUNGLE_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负责释放 - 本队列为有界队列,不适用于生产者速率持续大于消费者的场景