避免在树状结构中使用石膏

时间:2016-06-23 08:26:48

标签: c++ c++11 casting

我们正在考虑一种需要铸造感觉不对的设计。显然谷歌搜索了这个问题,但没有找到答案。希望了解如何避免铸造的建议。这是一个精简的例子(请执行任何拼写错误,这尚未编译)。

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;
};

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

'难点'是开发一种在不进行强制转换的情况下遍历此结构的算法。基类中的类型变量应标识正确的类型,并将强制转换为单个,但不会避免转换。

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

感谢任何评论。

4 个答案:

答案 0 :(得分:2)

您似乎正在重新实现的模式称为tagged union, or variant,它通常与访问者模式配对。我建议使用现有的实现,而不是自己动手。

但也要考虑替代实施:

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

    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总是返回一个空范围,而branch可以返回null键和值。或者,他们可以要求用户使用is_leaf,并在调用错误类型的函数时抛出异常。

    我没有直接返回对向量的访问,而是使用range类型,它应该是一对迭代器,它允许您封装底层容器的选择。

在所有这些设计中,您可以将键和值类型加以模板化,以获得更好的通用性。

答案 1 :(得分:1)

如果您有多个节点类型,请考虑使用访问者模式遍历所有树节点而不进行转换:

#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 Aggregate\n";
    }
    virtual void Finish(Aggregate& aggregate) override {
      std::cout << "Finish Aggregate\n";
    }
  } 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);
}

答案 2 :(得分:0)

我认为你想要Visitor模式。例如:

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方法来访问访问者并传递给自己进入访客。这是一种在C ++等单一调度语言中实现名为double dispatch的技术的方法。

答案 3 :(得分:0)

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;

该变体支持visit,这是一种类型安全的伪静态转换。

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