避免在树结构中使用铸造

Avoid using cast in tree structure

本文关键字:结构      更新时间:2023-10-16

我们正在考虑一种需要铸造的设计,这是不正确的。显然,谷歌搜索了这个问题,但没有找到答案。将感谢有关如何避免铸造需求的建议。这是一个被剥离的示例(请执行任何错别字,尚未编译)。

struct NodeBase
{
   enum class type
   {
     value,
     aggregate
   };
   type m_type;
};
struct NodeAggregate : public NodeBase
{
   std::vector<NodeBase*> m_list;
};
struct NodeValue : public NodeBase
{
   std::string m_key;
   std::string m_value;
};

上面的类可用于创建具有多个级别的树结构。

"困难"是开发一种算法,该算法在不进行铸造的情况下遍历了这种结构。基类中的类型变量应识别正确的类型,并将铸件的数量减少到单个,但不能避免铸造。

这个问题的替代设计是什么?

感谢任何评论。

您似乎要重新实现的模式称为标记的联合或变体,通常与访问者模式配对。我建议使用现有的实现,而不是自己滚动。

,但也考虑替代实现:

  • 使用均匀节点。让每个节点都能存储两个子列表和数据。这样,您只需要一种类型的节点,而无需铸造。如果只有叶子才能有数据,那么您可以在算法中实现该限制,而不是数据结构。

    struct Node
    {
         std::vector<Node*> m_list;
         std::string m_key;
         std::string m_value;
    };
    
  • 或使用虚拟函数:

    struct NodeBase
    {
        virtual       bool  is_leaf()            = 0;
        virtual const range children()     const = 0;
        virtual       range children()           = 0;
        virtual const std::string* key()   const = 0;
        virtual       std::string* key()         = 0; // if the tree is not sorted by key
        virtual const std::string* value() const = 0;
        virtual       std::string* value()       = 0;
        virtual ~NodeBase() {}
    };
    

    叶子和分支节点可以以不同的方式实现功能。Leaf总是可以返回空范围,而分支可以返回空键和值。另外,他们可以要求用户使用is_leaf,如果称为错误类型的功能。

    我使用的是一对迭代器,而不是直接返回向量访问向量,它可以使您可以封装基础容器的选择。

在所有这些设计中,您都可以将密钥和值类型用于更好的通用性。

如果您有几种节点类型考虑使用访问者模式在没有铸造的情况下穿越所有树节点:

#include <iostream>
#include <memory>
#include <string>
class Aggregate;
class ConcreteNode1;
class ConcreteNode2;
class Visitor {
public:
  virtual void Visit(ConcreteNode1& node) = 0;
  virtual void Visit(ConcreteNode2& node) = 0;
  virtual void Start(Aggregate& aggregate) = 0;
  virtual void Finish(Aggregate& aggregate) = 0;
};
class Node {
  friend class Aggregate;
public:
  Node() : parent_(nullptr) {}
  virtual ~Node() = default;
  virtual void Accept(Visitor& visitor) = 0;
private:
  void SetParent(Aggregate* parent) {
    parent_ = parent;
  }
  Aggregate* parent_;
};
class Aggregate : public Node {
public:
  void Add(std::shared_ptr<Node> node) {
    node->SetParent(this);
    nodes_.push_back(std::move(node));
  }
  virtual void Accept(Visitor& visitor) override {
    visitor.Start(*this);
    for (auto node : nodes_) {
      node->Accept(visitor);
    }
    visitor.Finish(*this);
  }
private:
  std::vector<std::shared_ptr<Node>> nodes_;
};
class ConcreteNode1 : public Node {
public:
  ConcreteNode1(int data) : data_(data) {}
  virtual void Accept(Visitor& visitor) override {
    visitor.Visit(*this);
  }
  int GetData() const { return data_; }
private:
  int data_;
};
class ConcreteNode2 : public Node {
public:
  ConcreteNode2(std::string name) : name_(std::move(name)) {}
  virtual void Accept(Visitor& visitor) override {
    visitor.Visit(*this);
  }
  const std::string& GetName() const { return name_; }
private:
  std::string name_;
};
int main()
{
  class SimpleVisitor : public Visitor {
    virtual void Visit(ConcreteNode1& node) override {
      std::cout << "ConcreteNode1: " << node.GetData() << std::endl;
    }
    virtual void Visit(ConcreteNode2& node) override {
      std::cout << "ConcreteNode2: " << node.GetName() << std::endl;
    }
    virtual void Start(Aggregate& aggregate) override {
      std::cout << "Start Aggregaten";
    }
    virtual void Finish(Aggregate& aggregate) override {
      std::cout << "Finish Aggregaten";
    }
  } visitor;
  auto root = std::make_shared<Aggregate>();
  root->Add(std::make_shared<ConcreteNode1>(1));
  {
    auto subtree = std::make_shared<Aggregate>();
    subtree->Add(std::make_shared<ConcreteNode1>(2));
    subtree->Add(std::make_shared<ConcreteNode2>("test1"));
    root->Add(subtree);
  }
  root->Add(std::make_shared<ConcreteNode2>("test2"));
  /// traverse through all nodes
  root->Accept(visitor);
}
struct NodeBase;
struct NodeAggregate {
  std::vector<std::unique_ptr<NodeBase>> children;
  ~NodeAggregate();
};
struct NodeValue {
  std::string key, value;
};
struct NodeBase {
  boost::variant<NodeAggregate, NodeValue> m;
};
inline NodeAggregate::~NodeAggregate() = default;

变体支持访问,这是一种类型的伪静态。

这也消除了所有不必要的间接。

我认为您想要访客模式。例如:

struct NodeAggregate;
struct NodeValue;
struct Visitor
{
    virtual void visit(NodeAggregate &aggregate) = 0;
    virtual void visit(NodeValue &value) = 0;
};
struct NodeBase
{
    virtual void accept(Visitor &visitor) = 0;
};
struct NodeAggregate : public NodeBase
{
    std::vector<NodeBase*> m_list;
    void accept(Visitor &visitor) override
    {
        visitor.visit(*this);
        for(auto base : m_list)
        {
            base->accept(visitor);
        }
    }
};
struct NodeValue : public NodeBase
{
    std::string m_key;
    std::string m_value;
    void accept(Visitor &visitor) override
    {
        visitor.visit(*this);
    }
};
struct WriteToConsoleVisitor : Visitor
{
    void visit(NodeAggregate &aggregate) override
    {
        std::cout << "got an aggregate" << std::endl;
    }
    void visit(NodeValue &value) override
    {
        std::cout << "got a value. key = " << value.m_key << ", value = " << value.m_value << std::endl;
    }
};

这里的诀窍是拥有一个为系统中的每个节点类型具有visit方法的访问者类,并且要使每个节点类型具有accept方法,该方法具有CC_4方法,该方法将带动访问者并将其自身传递到访问者中。这是一种在单个调度语言中实施一种称为Double Dispatch的技术,例如C 。