如何为Node的Leaf与NonLeaf派生类实现可复用的SplitNode函数?
问题描述
现有抽象基类Node,派生出Leaf和NonLeaf两个子类。需实现SplitNode函数,其逻辑对两个子类几乎完全一致,仅存在以下差异:
Leaf操作entries向量(类型为vector<unique_ptr<IndexEntry>>),通过entries[i]->r访问矩形属性NonLeaf操作children向量(类型为vector<unique_ptr<Node>>),通过children[i]->r访问矩形属性
要求避免复制粘贴重复代码,且SplitNode支持递归调用。
相关代码结构如下:
class Leaf; class Node { public: Node(); virtual Leaf& ChooseLeaf(const Rectangle& entry_r) = 0; // 使Node成为抽象类 Rectangle r; unique_ptr<Node> parent; }; class Leaf : public Node { public: Leaf(); Leaf& ChooseLeaf(const Rectangle& entry_r) override; vector<unique_ptr<IndexEntry>> entries; }; class NonLeaf : public Node { public: NonLeaf(); Leaf& ChooseLeaf(const Rectangle& entry_r) override; vector<unique_ptr<Node>> children; };
SplitNode的示例逻辑:
void SplitNode() { // Leaf分支: if (this.entries.size() > rtree.M) { ... } // NonLeaf分支: if (children.size() > rtree.M) { ... } // Leaf分支: entries[0]->r.DoSomething(); // NonLeaf分支: children[0]->r.DoSomething(); // 递归调用 parent.SplitNode(); ... }
解决方案
推荐采用以下几种方案统一SplitNode逻辑,避免重复代码:
方案1:多态接口抽象差异(最直观)
在基类Node中定义纯虚函数,将两个子类的差异点封装为统一接口,SplitNode作为基类的非虚函数调用这些接口,实现逻辑复用。
步骤1:修改基类Node
添加抽象差异点的接口,并实现统一的SplitNode函数:
class Node { public: Node(); virtual Leaf& ChooseLeaf(const Rectangle& entry_r) = 0; Rectangle r; unique_ptr<Node> parent; // 抽象差异点的接口 virtual size_t getElementCount() const = 0; virtual Rectangle& getElementRect(size_t index) = 0; // 若需修改元素,可添加对应接口,如removeElement、addElement等 // 统一的SplitNode函数,支持递归 void SplitNode() { // 统一判断逻辑 if (getElementCount() > rtree.M) { // ... 原有的分支逻辑 } // 统一访问矩形属性的逻辑 getElementRect(0).DoSomething(); // 递归调用父节点的SplitNode,需判断parent非空避免空指针 if (parent) { parent->SplitNode(); } // ... 其他统一逻辑 } };
步骤2:子类实现接口
在Leaf和NonLeaf中实现基类定义的纯虚函数:
class Leaf : public Node { public: // ... 原有代码 size_t getElementCount() const override { return entries.size(); } Rectangle& getElementRect(size_t index) override { return entries[index]->r; } }; class NonLeaf : public Node { public: // ... 原有代码 size_t getElementCount() const override { return children.size(); } Rectangle& getElementRect(size_t index) override { return children[index]->r; } };
此方案符合面向对象多态特性,递归调用直接通过parent->SplitNode()实现,parent作为Node*会自动分发到对应子类的接口实现。
方案2:CRTP静态多态(消除虚函数开销)
若担心虚函数的性能开销,可采用**奇异递归模板模式(CRTP)**实现静态多态,通过模板参数推导实现差异点的复用:
步骤1:定义CRTP基类模板
class Node { public: Node(); virtual Leaf& ChooseLeaf(const Rectangle& entry_r) = 0; Rectangle r; unique_ptr<Node> parent; // 定义纯虚的SplitNode,确保递归调用正常 virtual void SplitNode() = 0; }; template<typename Derived> class NodeCRTP : public Node { public: using Node::Node; void SplitNode() override { Derived* derived = static_cast<Derived*>(this); // 统一逻辑,调用子类的具体实现 if (derived->getElementCount() > rtree.M) { // ... 原有的分支逻辑 } derived->getElementRect(0).DoSomething(); if (parent) { parent->SplitNode(); } // ... 其他统一逻辑 } };
步骤2:子类继承CRTP模板并实现接口
class Leaf : public NodeCRTP<Leaf> { public: Leaf(); Leaf& ChooseLeaf(const Rectangle& entry_r) override; vector<unique_ptr<IndexEntry>> entries; size_t getElementCount() const { return entries.size(); } Rectangle& getElementRect(size_t index) { return entries[index]->r; } }; class NonLeaf : public NodeCRTP<NonLeaf> { public: NonLeaf(); Leaf& ChooseLeaf(const Rectangle& entry_r) override; vector<unique_ptr<Node>> children; size_t getElementCount() const { return children.size(); } Rectangle& getElementRect(size_t index) { return children[index]->r; } };
此方案通过静态类型推导避免了虚函数的运行时开销,同时保证递归调用的正确性。
扩展说明
若SplitNode中还有其他差异点(如创建新节点、拆分后元素分配等),可同样将这些逻辑封装为接口(虚函数或CRTP方法),进一步复用代码。
内容的提问来源于stack exchange,提问作者dimitris93
相关产品推荐
相关产品推荐

