绝大多数情况下不需要手写享元类——字符串字面量、string_view、shared_ptr、对象池等更轻量直接;仅当对象满足“内部状态稳定+外部状态频繁变化+创建开销大”三条件时才值得考虑,且应优先用shared_ptr显式管理共享引用。

享元模式在 C++ 里到底该不该手写 flyweight 类?
绝大多数情况下,不需要——C++ 标准库没提供 flyweight,Boost.Flyweight 又太重,而真正需要共享的细粒度对象,往往已有更轻量、更直接的替代方案。
比如字符串字面量自动驻留("hello" 全局唯一)、std::string_view 避免拷贝、std::shared_ptr 管理共享状态,甚至用 std::unordered_set + std::make_shared 手动做对象池,都比从零实现享元更可控、更易调试。
- 手写享元类容易陷入“为模式而模式”,把简单对象拆成
intrinsic和extrinsic两部分,反而增加间接层和生命周期管理负担 - 真正高频创建/销毁的小对象(如字符、像素、网格顶点),通常更适合用对象池(
ObjectPool)或内存池(std::pmr::memory_resource)来控制分配,而非共享逻辑 - Boost.Flyweight 虽然封装了共享逻辑,但默认使用
std::map查表,对高频访问场景可能成为性能瓶颈;换用boost::flyweights::no_locking或自定义哈希策略又得深入源码
C++ 中哪些场景真值得上享元?
只有当对象具备「内部状态稳定 + 外部状态频繁变化 + 创建开销显著」三个条件时,才值得考虑享元。典型例子是文本编辑器里的字符格式(font size / color / bold),或游戏引擎中的材质描述(shader name / texture path)。
这类对象的不变部分(如字体名、着色器路径)可共享,变的部分(如当前光标位置、渲染实例 ID)必须外部传入。这时关键不是“怎么写 FlyweightFactory”,而是“怎么隔离可共享与不可共享的数据”。
立即学习“C++免费学习笔记(深入)”;
- 用
struct显式拆分:把所有 const 字段(const std::string& font_name)放进享元类,把非 const 字段(int cursor_offset)留在客户端 - 工厂函数返回
std::shared_ptr<const FontInfo>,而不是裸指针,避免误删共享实例 - 注意线程安全:如果多个线程并发调用工厂获取同一 key 的享元,查表逻辑必须加锁(
std::mutex)或用std::call_once初始化单例缓存
std::shared_ptr 能不能直接当享元用?
能,而且很多时候更合适。享元本质是“复用对象引用”,而 std::shared_ptr 正是为此设计的轻量引用计数机制。
例如,你有一组预定义的粒子效果配置:ParticleConfig,共 12 种。与其写个 ParticleConfigFactory 维护 map,不如直接声明:
static const auto smoke_config = std::make_shared<const ParticleConfig>(/*...*/);
然后各处用 smoke_config 赋值即可。没有查找开销,没有类型擦除,编译期确定地址。
- 别用
std::weak_ptr做享元缓存——它不阻止对象析构,共享语义就断了 - 如果配置数据来自运行时加载(如 JSON),再用
std::unordered_map<std::string, std::shared_ptr<const T>>缓存,key 是文件路径或 hash 值,不是对象内容本身 - 警惕
std::shared_ptr的控制块分配开销:对百万级小对象,优先考虑内存池(std::pmr::polymorphic_allocator)配合std::shared_ptr
为什么 C++ 项目里很少看到标准享元实现?
因为 C++ 的内存模型和 RAII 机制天然鼓励“按需构造 + 明确生命周期”,而享元隐含的“全局共享 + 隐式依赖”容易破坏这点。
比如一个 TextureFlyweight 对象被多个模块持有,但某个模块悄悄调用了 reload(),其他模块就拿到脏数据;或者工厂缓存没清空,导致热更新后仍用旧资源。
- 真正的难点不在“怎么共享”,而在“谁负责释放”和“什么时候失效”——这两点 C++ 没有语言级保障
- 现代 C++ 更倾向用值语义(
std::string,std::vector)+ 移动语义规避拷贝,比共享更直观、更难出错 - 如果你正在重构遗留代码,发现大量
new出来的相同小对象,优先检查是否能用栈对象、std::array或std::variant替代,而不是立刻套享元
共享不是银弹,尤其是当“共享什么”和“谁说了算”还没理清的时候。



















