当前位置: 首页 > news >正文

享元模式,用Qt/C++绘制森林

一、为什么用享元模式

在开发一些资源密集型应用时,经常会遇到大量对象占用内存的问题。比如在绘制一片森林时,我们可能需要生成上千棵树,如果每棵树都包含完整的绘制信息、纹理数据,那么内存占用会非常高。

享元模式的核心思想是共享对象的内在状态,将可以复用的部分提取出来,多个外部对象共同使用,从而大幅减少内存占用。  

二、场景说明

我们要实现一个森林绘制程序,要求:

  • 种植多种树(松树、橡树、枫树、椰子树),并能绘制2000棵树。

  • 每种树都有不同的形状和颜色。

  • 每种树的绘制信息(包括占用大量内存的纹理数据)可以共享。

  • 每棵树的位置是外部状态,不参与共享。

  • 当窗口大小变化时,森林能够重新生成并适应新的尺寸。

在我们的场景中,每种树(松树、橡树、枫树、椰子树)都有固定的形状、颜色和大块纹理数据(4M),这些是可以共享的内在状态;而每棵树的坐标位置是外在状态,需要单独存储。

如果不使用享元模式,每棵树都占用大量内存,整个程序可能会因为2000棵树而消耗约8GB内存。

三、类图

四、C++代码实现

#include <QApplication>
#include <QPainter>
#include <QResizeEvent>
#include <QWidget>
#include <map>
#include <memory>
#include <random>
#include <vector>// ================== 外在状态 ==================
struct Position {int x, y;Position(int x, int y) : x(x), y(y) {}
};// ================== 享元接口 ==================
class TreeFlyweight {public:virtual void draw(QPainter& painter, const Position& pos) = 0;virtual ~TreeFlyweight() = default;
};// ================== 具体享元 ==================
class ConcreteTreeFlyweight : public TreeFlyweight {private:QColor color;QString type;// 模拟占用大内存的数据,比如树的纹理std::vector<int> heavyData;public:ConcreteTreeFlyweight(const QString& type, const QColor& color): type(type), color(color) {// 模拟占用内存:1百万个整数,大约 4MBheavyData.resize(1000000, 42);}void draw(QPainter& painter, const Position& pos) override {// 画树干painter.setBrush(Qt::darkGray);painter.drawRect(pos.x - 2, pos.y, 4, 20);painter.setBrush(color);painter.setPen(Qt::NoPen);if (type == "松树") {// 圆形树冠painter.drawEllipse(pos.x - 10, pos.y - 20, 20, 20);} else if (type == "橡树") {// 椭圆形树冠painter.drawEllipse(pos.x - 15, pos.y - 20, 30, 20);} else if (type == "枫树") {// 三角形树冠QPolygon triangle;triangle << QPoint(pos.x, pos.y - 25) << QPoint(pos.x - 15, pos.y - 5)<< QPoint(pos.x + 15, pos.y - 5);painter.drawPolygon(triangle);} else if (type == "椰子树") {// 半圆形树冠painter.drawPie(pos.x - 15, pos.y - 20, 30, 30, 0, 180 * 16);} else {// 默认:圆形painter.drawEllipse(pos.x - 10, pos.y - 20, 20, 20);}}
};// ================== 享元工厂 ==================
class TreeFactory {private:std::map<QString, std::shared_ptr<TreeFlyweight>> pool;public:std::shared_ptr<TreeFlyweight> getTree(const QString& type,const QColor& color) {QString key = type + "_" + color.name();if (pool.find(key) == pool.end()) {pool[key] = std::make_shared<ConcreteTreeFlyweight>(type, color);}return pool[key];}int getPoolSize() const { return pool.size(); }
};// ================== 客户端 ==================
class ForestWidget : public QWidget {private:struct Tree {std::shared_ptr<TreeFlyweight> flyweight;Position pos;Tree(std::shared_ptr<TreeFlyweight> f, Position p) : flyweight(f), pos(p) {}};std::vector<Tree> trees;TreeFactory& factory;public:ForestWidget(TreeFactory& f, QWidget* parent = nullptr): QWidget(parent), factory(f) {resize(800, 600);generateTrees(width(), height());  // 初始生成}void plantTree(const QString& type, const QColor& color, int x, int y) {auto tree = factory.getTree(type, color);trees.emplace_back(tree, Position(x, y));}// ================== 生成森林 ==================void generateTrees(int w, int h) {trees.clear();  // 清空旧的树// 随机数引擎std::mt19937 rng(std::random_device{}());std::uniform_int_distribution<int> distX(20, w - 20);std::uniform_int_distribution<int> distY(50, h - 50);std::uniform_int_distribution<int> distType(0, 3);for (int i = 0; i < 2000; ++i) {int t = distType(rng);if (t == 0) {plantTree("松树", Qt::green, distX(rng), distY(rng));} else if (t == 1) {plantTree("橡树", QColor(0, 128, 0), distX(rng), distY(rng));} else if (t == 2) {plantTree("枫树", QColor(255, 69, 0), distX(rng), distY(rng));} else {plantTree("椰子树", QColor(34, 139, 34), distX(rng), distY(rng));}}}protected:void paintEvent(QPaintEvent*) override {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);for (auto& t : trees) {t.flyweight->draw(painter, t.pos);}painter.setPen(Qt::black);painter.drawText(10, 20,QString("享元池对象数: %1, 实际种植树木数: %2").arg(factory.getPoolSize()).arg(trees.size()));}// ================== 重写 resizeEvent ==================void resizeEvent(QResizeEvent* event) override {generateTrees(event->size().width(), event->size().height());update();  // 触发重绘QWidget::resizeEvent(event);}
};// ================== main ==================
int main(int argc, char* argv[]) {QApplication app(argc, argv);TreeFactory factory;ForestWidget forest(factory);forest.show();return app.exec();
}

五、森林效果展示

使用享元模式,由约8G内存缩小到约50M,✿✿ヽ(°▽°)ノ✿

六、总结

  • 问题背景:在需要大量对象的场景下,内存消耗可能很大。

  • 解决方案:享元模式将可共享的内在状态提取出来,多个对象复用,减少内存占用。

  • 示例优势

    • 上千棵树只使用了 4~5 个享元对象(每种树一份大数据)。

    • 内存消耗大幅下降,绘制效率高。

    • 外部状态(位置)单独存储,实现灵活布局。

  • 扩展思路

    • 可将树的纹理、3D 模型数据、图标等抽象为共享对象。

    • 适用于游戏、地图渲染、图表绘制等高对象密度场景。

通过这个示例,读者可以直观感受到享元模式在节省内存、提高性能方面的实际价值。


文章转载自:

http://IKgNFKKm.xssbt.cn
http://GJvE6DQy.xssbt.cn
http://9izhp06Z.xssbt.cn
http://fIbUT4OE.xssbt.cn
http://fq5JQzWP.xssbt.cn
http://XVTU7pcE.xssbt.cn
http://RosJfR8q.xssbt.cn
http://tH2PAyGz.xssbt.cn
http://g7PqUzyG.xssbt.cn
http://BwNQykXd.xssbt.cn
http://TlblcDg3.xssbt.cn
http://VMeaMlq6.xssbt.cn
http://KMd6C3K8.xssbt.cn
http://lLTP7DIr.xssbt.cn
http://H7MC0Urc.xssbt.cn
http://i5zs0Y6H.xssbt.cn
http://Tx4uyKXR.xssbt.cn
http://RcH8PRSt.xssbt.cn
http://bBj1teuX.xssbt.cn
http://tuQYXF4C.xssbt.cn
http://Qq90pLhw.xssbt.cn
http://zKVi4LJW.xssbt.cn
http://k3sfeYIh.xssbt.cn
http://nN4B7YaF.xssbt.cn
http://hKGJkCBa.xssbt.cn
http://hL4iFKSL.xssbt.cn
http://c7GfGzkl.xssbt.cn
http://7zlnEcnW.xssbt.cn
http://EG8iYLxr.xssbt.cn
http://E83zYEJ9.xssbt.cn
http://www.dtcms.com/a/375360.html

相关文章:

  • GO RPC 教学文档
  • Atlantis Word Processor:全方位的文字处理专家
  • [iOS] 单例模式的深究
  • 视频通话实现语音转文字
  • String-HashCode源码分析
  • 深入浅出C++继承机制:从入门到实战
  • 级联框的实现
  • android 性能优化—内存泄漏,内存溢出OOM
  • 从PyTorch到ONNX:模型部署性能提升
  • JAVA:实现快速排序算法的技术指南
  • SQL 触发器从入门到进阶:原理、时机、实战与避坑指南
  • 无标记点动捕技术:重塑展厅展馆的沉浸式数字交互新时代
  • 【Agent】DeerFlow Planner:执行流程与架构设计(基于真实 Trace 深度解析)
  • R语言读取excel文件数据-解决na问题
  • 在钉钉上长出的AI组织:森马的路径与启示
  • IntelliJ IDEA 中 JVM 配置参考
  • JVM(二)--- 类加载子系统
  • 9.ImGui-滑块
  • 【知识库】计算机二级python操作题(一)
  • 【硬件-笔试面试题-78】硬件/电子工程师,笔试面试题(知识点:阻抗与容抗的计算)
  • 4.5Vue的列表渲染
  • 使用YOLO11进行路面裂缝检测
  • 常见并行概念解析
  • 9月9日
  • centos系统上部署安装minio
  • 下载CentOS 7——从阿里云上下载不同版本的 CentOS 7
  • 《预约一团乱麻?预约任务看板让你告别排班噩梦!宠物店效率翻倍指南》
  • Shell 脚本条件测试与 if 语句
  • 【倒数日子隐私收集】
  • Diamond基础4:仿真流程、添加原语IP核