我们正在考虑一种需要铸造感觉不对的设计。显然谷歌搜索了这个问题,但没有找到答案。希望了解如何避免铸造的建议。这是一个精简的例子(请执行任何拼写错误,这尚未编译)。
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;
};
上述类可用于创建具有多个级别的树结构。
'难点'是开发一种在不进行强制转换的情况下遍历此结构的算法。基类中的类型变量应标识正确的类型,并将强制转换为单个,但不会避免转换。
这个问题的替代设计是什么?
感谢任何评论。
答案 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,这是一种类型安全的伪静态转换。
这也消除了所有不必要的间接。