通八洲科技

c++如何实现享元模式 c++设计模式之Flyweight【实例】

日期:2025-12-27 00:00 / 作者:裘德小鎮的故事
享元模式通过共享内部状态、分离外部状态来减少内存占用。C++中以字符渲染为例,定义享元接口和具体类,工厂用map缓存实例,客户端通过工厂获取并传入外部状态调用display。

享元模式(Flyweight)在 C++ 中主要用于减少内存占用,通过共享大量细粒度对象来避免创建过多重复实例。它的核心是将对象状态分为 内部状态(可共享、不可变)和 外部状态(不可共享、由客户端维护)。下面用一个典型场景——文字编辑器中字符渲染——来演示如何用 C++ 实现享元模式。

享元接口与具体享元类

定义统一接口,让所有享元对象对外提供一致的使用方式。内部状态(如字体、字号、颜色)在构造时固定,不随上下文变化:

class CharacterFlyweight {
public:
    virtual ~CharacterFlyweight() = default;
    virtual void display(char c, int x, int y) const = 0; // 外部状态:位置、具体字符
};

class ConcreteCharacter : public CharacterFlyweight { private: const char m_font; const int m_size; const std::string m_color;

public: ConcreteCharacter(char font, int size, const std::string& color) : m_font(font), m_size(size), m_color(color) {}

void display(char c, int x, int y) const override {
    std::cout << "Char '" << c 
              << "' at (" << x << "," << y << ") "
              << "with font " << m_font 
              << ", size " << m_size 
              << ", color " << m_color << "\n";
}

};

享元工厂:管理共享对象池

工厂负责按需创建或复用享元对象。用 std::mapstd::unordered_map 缓存已创建的实例,键通常由内部状态组合构成:

class CharacterFactory {
private:
    std::map, std::unique_ptr> m_pool;

public: CharacterFlyweight* getCharacter(char font, int size, const std::string& color) { auto key = std::make_tuple(font, size, color); if (m_pool.find(key) == m_pool.end()) { m_pool[key] = std::make_unique(font, size, color); } return m_pool[key].get(); } };

客户端代码:使用享元而非直接 new

客户端不再自行构造享元,而是向工厂请求;每次调用 display() 传入外部状态(坐标、实际字符),实现“一份享元,多次显示”:

int main() {
    CharacterFactory factory;
// 获取两个相同样式的享元(实际只创建一次)
CharacterFlyweight* bold12red = factory.getCharacter('B', 12, "red");
CharacterFlyweight* bold12red2 = factory.getCharacter('B', 12, "red"); // 复用

// 不同样式会创建新实例
CharacterFlyweight* italic10blue = factory.getCharacter('I', 10, "blue");

// 渲染不同位置的相同样式字符
bold12red->display('H', 0, 0);
bold12red2->display('e', 1, 0);
italic10blue->display('l', 2, 0);

return 0;

}

关键注意事项

  • 内部状态必须是 不可变的(建议用 const 成员),否则共享会导致逻辑错误
  • 享元对象应是 无状态的或仅依赖传入的外部参数,不能持有上下文相关数据
  • 工厂类可加线程安全封装(如加锁),适用于多线程环境
  • 若享元数量极少或生命周期极短,引入享元反而增加复杂度,需权衡

这个例子展示了享元模式如何在 C++ 中落地:用工厂控制创建、用接口解耦使用、靠内部/外部状态分离实现高效复用。不复杂但容易忽略细节。