享元对象的内部状态必须为const且无副作用,外部状态须由客户端传入operation();工厂需线程安全,优先用整型key和预加载,operation()函数应全为const。

享元对象的内部状态必须是 const 且无副作用
享元模式的核心是把对象中可共享的部分(内部状态)和不可共享的部分(外部状态)拆开。C++ 中,内部状态一旦被多个享元实例共用,就绝不能在运行时修改——否则会引发数据竞争或逻辑错乱。
常见错误是把 std::string 或 std::vector 成员声明为非 const,又在 operation() 中调用 push_back() 或 append();这看似方便,实则破坏享元契约。
- 内部状态成员全部用
const修饰,构造时一次性初始化 - 避免使用引用计数智能指针(如
std::shared_ptr)管理内部状态,除非你明确需要延迟释放——它会掩盖所有权边界 - 若内部状态含指针,确保指向的是只读内存(如字符串字面量、static const 数据)
享元工厂必须线程安全且避免重复构造
多线程环境下,两个线程同时调用 getFlyweight(key) 可能各自新建相同内部状态的对象,导致内存浪费甚至不一致。C++11 起,std::call_once 和 std::once_flag 是最轻量的解决方案,比全局互斥锁更高效。
示例关键片段:
class FlyweightFactory {
static std::unordered_map<:string std::unique_ptr>> pool_;
static std::once_flag init_flag_;
static void init_pool() {
// 预加载常用 key 对应的享元,避免首次访问锁竞争
pool_["red"] = std::make_unique<concreteflyweight>("red", 0xFF0000);
pool_["blue"] = std::make_unique<concreteflyweight>("blue", 0x0000FF);
}
public:
static const Flyweight& getFlyweight(const std::string& key) {
std::call_once(init_flag_, &FlyweightFactory::init_pool);
auto it = pool_.find(key);
if (it != pool_.end()) return *it->second;
throw std::runtime_error("Unknown flyweight key: " + key);
}
};</concreteflyweight></concreteflyweight></:string>
- 不要在
getFlyweight()里用std::lock_guard包裹整个查找+构造流程——高并发下会成为瓶颈 - 预加载(warm-up)比懒加载更可控;若 key 空间不可预知,改用
std::shared_mutex保护pool_的读写 - 返回引用而非指针,避免调用方误删或悬挂
外部状态必须由客户端显式传入 operation()
享元自身不保存坐标、ID、临时颜色偏移等随上下文变化的数据。这些必须由调用方在每次 operation() 时传入——这是模式能否真正节省内存的关键。
典型反例:
class BadFlyweight {
const std::string name_;
int x_, y_; // ❌ 错误:x/y 是外部状态,不该放这里
public:
BadFlyweight(const std::string& n) : name_(n) {}
void render() { /* 用 x_, y_ 渲染 */ } // 无法复用
};
正确做法:
class GoodFlyweight {
const std::string name_;
const uint32_t color_;
public:
GoodFlyweight(const std::string& n, uint32_t c) : name_(n), color_(c) {}
void render(int x, int y) const { /* 用 x, y 渲染 */ } // ✅ 外部状态由调用方提供
};
- 所有
operation()成员函数应标记为const,强制约束不修改内部状态 - 若外部状态结构复杂(如带旋转角、缩放因子),建议封装为
struct一次性传入,避免参数列表爆炸 - 切勿在享元内部缓存外部状态(例如用
thread_local存 last_x),这会让行为变得隐晦且难以测试
std::unordered_map 查找性能受 key 类型影响大
享元工厂底层几乎都用哈希表做 key→享元映射,但 std::string 作为 key 会触发多次内存分配和字符比较,尤其在 key 很长或数量极大时(比如百万级粒子系统),可能成为热点。
- 优先用整型 key(如
enum class或uint32_t)代替字符串;枚举可配合constexpr字符串映射表实现可读性 - 若必须用字符串,考虑自定义哈希器,比如对短字符串(≤16 字节)走 SSO 内联哈希,跳过
std::hash<:string></:string>的迭代开销 - 避免在热循环中反复构造临时
std::string作为 key;复用一个static thread_local std::string缓冲区,或直接传std::string_view
真正大规模场景下,享元是否值得,取决于内部状态复用率与哈希查找成本的平衡——不是所有“看起来像享元”的地方都该硬套这个模式。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











