享元模式的核心是分离内部状态与外部状态:内部状态(如glyph、font_family、font_size)只读且不可变,外部状态(如x、y、is_selected)由客户端每次调用时传入,避免共享失效和并发冲突。

享元对象必须分离内部状态和外部状态
享元模式的核心不是“共享对象”,而是“把能共享的抽出来,不能共享的传进来”。内部状态(intrinsic state)必须是只读、不可变、与上下文无关的;外部状态(extrinsic state)则每次使用时由客户端传入。常见错误是把 position、color 这类随场景变化的字段塞进享元类里——这会导致共享失效,甚至引发并发写冲突。
正确做法是:享元类只保留 glyph 字符、font_family、font_size 这类真正可复用的属性;而 x、y、is_selected 等必须由调用方在 render() 时传入。
示例片段:
class CharacterFlyweight {
private:
char m_glyph;
std::string m_font_family;
int m_font_size; // 内部状态:构造时固定,不修改
public:
CharacterFlyweight(char g, const std::string& f, int s)
: m_glyph(g), m_font_family(f), m_font_size(s) {}
void render(int x, int y, bool is_selected) const { // 外部状态走参数
std::cout
<h3>享元工厂要用键值唯一标识享元实例</h3>
<p>工厂不是“缓存任意对象”,而是按内部状态组合生成唯一键,再查表复用。键类型选错或拼接逻辑有歧义,就会导致该复用没复用,或不该复用却复用了。比如用 <code>std::tuple<char std::string int></char></code> 当键比拼接字符串更安全——避免 <code>"11"</code> 和 <code>"1,1"</code> 这类歧义。</p>
<p>工厂还必须是线程安全的(如果多线程使用),但别一上来就加全局锁。小项目用 <code>static std::map</code> + <code>std::call_once</code> 初始化足够;高并发场景建议用 <code>std::shared_mutex</code> 或无锁哈希表。</p>
<p>关键点:</p>
-
get_flyweight()参数只接受内部状态字段,不接受外部状态 - 键生成逻辑必须幂等且无副作用(例如不要在构造键时 new 对象)
- 返回引用或智能指针,禁止返回栈对象地址
客户端调用时必须显式传递外部状态
这是最容易被忽略的一环:开发者写了享元和工厂,却在渲染循环里把坐标硬编码进享元对象,等于白搭。享元的 render()、hit_test()、layout() 等方法,所有依赖上下文的参数都得由外部传入。
典型误用:
// ❌ 错误:把 x/y 存进享元,破坏共享性 flyweight->set_position(x, y); // 不该存在 flyweight->render(); // 无法复用
正确方式:
// ✅ 正确:每次调用带上下文
auto& fw = factory.get_flyweight('A', "Consolas", 12);
fw.render(cursor_x, cursor_y, is_focused);
fw.render(line_x + 10, line_y + 20, false);
注意:如果外部状态特别多(比如 5 个以上参数),可封装成结构体传入,但结构体本身不应被享元持有或缓存。
C++ 中 shared_ptr 是享元工厂的合理返回类型
用 std::shared_ptr<const characterflyweight></const> 而不是裸指针或引用,既避免生命周期管理问题,又支持多线程安全共享。工厂内部用 std::map<key std::shared_ptr t>></key> 存储,新对象只构造一次,后续直接拷贝 shared_ptr ——开销极小,且自动管理内存。
不推荐返回引用,因为工厂可能在后续扩容或重哈希时使引用失效;也不推荐返回值(复制整个对象),违背享元本意。
一个小陷阱:如果享元类有虚函数,记得把析构函数设为 virtual,否则通过 shared_ptr<base> 持有时派生类资源不会被正确释放。
复杂点在于——外部状态若涉及资源(如纹理句柄、GPU buffer ID),仍需由客户端统一管理,享元绝不持有这些。这点容易被图形/游戏场景忽略,结果看似共享了字符对象,实则每个还在偷偷绑定独立纹理。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











