C++ 实战:构建通用的层次化数据模型 (Hierarchical Data Model)

在开发大型 C++ 桌面应用(如组态软件、编辑器、CAD 系统)时,我们经常面临一个核心挑战:如何高效、统一地管理内存中复杂的异构对象树?

我们需要一个能够容纳不同类型业务对象(如设备、工程、图元)的容器,同时还需要支持层级关系、自动化内存管理、事件通知以及序列化。

本文将深入剖析一个基于 C++11 实现的通用数据模型框架。该设计采用了组合模式访问者模式观察者模式,实现了一个高内聚、低耦合的"内存对象数据库"。


1. 核心架构设计

系统的核心思想是将"树形拓扑结构"与"具体业务数据"分离。通过继承和模板技术,实现类型擦除与统一管理。

类关系图谱

  • DataNode (基类): 抽象节点。定义了父子关系、ID 生成、树遍历等通用行为。
  • DataDocument (管理器): 树的根节点。负责全局上下文管理、工厂创建方法以及事件分发。
  • DataElement<T> (适配器) : 泛型子类。将具体的业务类 T 包装为树节点,使其能够挂载到 DataNode 树上。
  • DataVisitor (访问者): 提供了一种安全访问内部原始数据的机制。

2. 关键技术实现

2.1 异构对象的统一容器 (Type Erasure)

在 C++ 中,不同类型的对象难以存放在同一个容器中。本项目通过 DataNode 指针实现了多态存储。

cpp 复制代码
// 1. 定义抽象基类,只包含通用接口
class DataNode {
    // 存储子节点,使用 shared_ptr 自动管理生命周期
    std::vector<std::shared_ptr<DataNode>> _listOfChildren;
public:
    virtual ~DataNode() {}
    void AppendChild(const std::shared_ptr<DataNode> &newChild);
};

// 2. 定义泛型适配器,持有具体数据
template<class T>
class DataElement : public DataNode {
    std::shared_ptr<T> _attachedInstance; // 真正的业务对象
public:
    DataElement() : _attachedInstance(new T()) {}
    // 转发调用到具体业务对象
    virtual wstring ModuleID() { return _attachedInstance->ModuleID(); }
};

设计优势

  • 统一性 :无论业务对象是 Device 还是 Project,在树结构中统统被视为 DataNode
  • 扩展性 :新增业务类型无需修改核心树代码,只需实例化 DataElement<NewType> 即可。

2.2 智能指针与生命周期管理 (RAII)

传统的 C++ 树结构容易出现内存泄漏或野指针。本项目全面采用了 C++11 智能指针:

  • 强引用 :父节点持有子节点的 std::shared_ptr,保证子节点生命周期跟随父节点。
  • 内存优化技巧 :在析构或清空节点时,使用了 Swap Trick 彻底释放 vector 内存。
cpp 复制代码
// DataNode.cpp
void DataNode::RemoveAll() {
    // 创建临时空 vector 并交换,强制释放 capacity 内存
    std::vector<std::shared_ptr<DataNode>>().swap(_listOfChildren);
}

2.3 基于回调的事件驱动机制 (Observer Pattern)

为了实现数据与视图(UI)的解耦,DataDocument 提供了基于 std::function 的事件回调。

cpp 复制代码
// DataDocument.h
typedef std::function<void(DataDocument*, DataNode*)> DataNodeFunction;

class DataDocument : public DataNode {
    DataNodeFunction _pNodeInserted; // 节点插入回调
public:
    void SetNodeInserted(DataNodeFunction pFunc) { _pNodeInserted = pFunc; }
    
    // 当有节点插入时触发
    void DataNodeInserted(DataNode *insertedNode) {
        if (_pNodeInserted) _pNodeInserted(this, insertedNode);
    }
};

应用场景

UI 层只需在初始化时调用 doc->SetNodeInserted(UpdateTreeView)。当数据层发生 AppendChild 操作时,UI 会自动刷新,无需数据层感知 UI 的存在。

2.4 工厂模式与"上户口"

为了确保每个节点都能正确关联到文档(Document),节点不能直接 new,而必须通过工厂方法创建。

cpp 复制代码
// DataDocument.h
template<typename T>
shared_ptr<DataElement<T>> CreateNode() {
    // 1. 创建节点
    shared_ptr<DataElement<T>> myNode(new DataElement<T>());
    // 2. 转换为基类指针
    shared_ptr<DataNode> pNode = dynamic_pointer_cast<DataNode>(myNode);
    // 3. 关键:设置所属文档指针 (Dependency Injection)
    pNode->Set_DocumentElement(this);
    return myNode;
}

3. 序列化接口设计

系统预留了标准的序列化接口,用于支持 XML/JSON 持久化。

cpp 复制代码
// DataElement.h
virtual shared_ptr<NERC::Foundation::Parameter> ParserToParameter(Database* ptrDatabase) { 
    // 委托给具体的业务对象 T 进行序列化
    return _attachedInstance->ParserToParameter(ptrDatabase); 
};

这种设计将序列化逻辑下沉到具体的业务类 T 中,DataElement 仅作为透明的转发层。


4. 总结

这套框架展示了成熟的 C++ 工程化思维:

  1. 利用多态和模板 解决了类型系统的刚性问题。
  2. 利用 RAII 和智能指针 解决了手动内存管理的风险。
  3. 利用回调函数 实现了模块间的彻底解耦。

它非常适合作为 组态软件、节点编辑器、文件系统管理 等复杂层次化应用的底层数据内核。

相关推荐
一起养小猫7 小时前
Flutter for OpenHarmony 实战:打造天气预报应用
开发语言·网络·jvm·数据库·flutter·harmonyos
安全二次方security²7 小时前
CUDA C++编程指南(7.25)——C++语言扩展之DPX
c++·人工智能·nvidia·cuda·dpx·cuda c++编程指南
xyq20247 小时前
Java 抽象类
开发语言
爱装代码的小瓶子7 小时前
【c++与Linux基础】文件篇(4)虚拟文件系统VFS
linux·开发语言·c++
疯狂的喵12 小时前
C++编译期多态实现
开发语言·c++·算法
2301_7657031413 小时前
C++中的协程编程
开发语言·c++·算法
m0_7487080513 小时前
实时数据压缩库
开发语言·c++·算法
小魏每天都学习13 小时前
【算法——c/c++]
c语言·c++·算法
lly20240613 小时前
jQuery Mobile 表格
开发语言
惊讶的猫13 小时前
探究StringBuilder和StringBuffer的线程安全问题
java·开发语言