basically, I want to implement document type converter. I've designed pretty straight-forward solution:
DocTypeParser : Parser
will converts file into tree structure of nodes, representing different elements (headers, lists, bold texts, ...)DocTypePrinter : Printer
will deconstruct that tree back into text fileSo far so good, but I came across nasty problem - The connection between tree nodes is estabilished through std::vector<Node *>
and I am not sure how to determine what child class is being processed.
My demo code:
class Node
{
public:
Node()
{
}
~Node()
{
for (auto it : Leaf)
delete it;
}
Node &Add(Node *leaf)
{
Leaf.push_back(leaf);
return *this;
}
std::vector<Node *> Leaf;
};
class NodeA : public Node
{
public:
NodeA() : Node()
{
}
};
class Printer
{
public:
Printer() = default;
std::string Print(Node &n)
{
int i = 0, k = n.Leaf.size();
std::string res = "<n>";
for (; i < k; ++i)
res += Print(*(n.Leaf[i]));
res += "</n>";
return res;
}
std::string Print(NodeA &n)
{
int i = 0, k = n.Leaf.size();
std::string res = "<A>";
for (; i < k; ++i)
res += Print(*(n.Leaf[i]));
res += "</A>";
return res;
}
};
int main(int argc, const char *argv[])
{
NodeA tree;
tree.Add(new NodeA).Add(new NodeA);
Printer p;
std::cout << p.Print(tree) << std::endl;
return 0;
}
Desired result: <A><A></A><A></A></A>
Actual result: <A><n></n><n></n></A>
I pretty much understand what is the problem (vector stores Node
pointers, not NodeChild
pointers), but not that sure how to overcome that. dynamic_cast
seems to be not-the-solution-at-all.
So finally question - is there cure for me or am I longing for the wrong design altogether?
You used type erasure wrongly. Your nodes accessed by Node*
, so *(n.Leaf[i])
expression returns type Node
, not NodeA
.
What you do resembles visitor pattern, to recognize which class is which you have to use a virtual method in Node class and override it in NodeA, calling it with dispatcher as argument (classic visitor) or calling it from dispatcher you can recognize which instance is which.
In first case node would call the Print method and pass it *this
.
This is minimal rework of your code, but I think, it needs honing\optimizing. Depends on what your actual task is, vistor might be a little too excessive.
#include <string>
#include <iostream>
#include <vector>
class Node;
class NodeA;
class AbstractPrinter
{
public:
virtual std::string Print(Node &n) =0;
virtual std::string Print(NodeA &n) =0;
};
class Node
{
public:
Node()
{
}
virtual ~Node()
{
for (auto it : Leaf)
delete it;
}
Node &Add(Node *leaf)
{
Leaf.push_back(leaf);
return *this;
}
virtual std::string Print(AbstractPrinter& p)
{
return p.Print(*this);
}
std::vector<Node *> Leaf;
};
class NodeA : public Node
{
public:
NodeA() : Node()
{
}
// if not override this, it would use Node
virtual std::string Print(AbstractPrinter& p) override
{
return p.Print(*this);
}
};
class Printer : public AbstractPrinter
{
public:
Printer() = default;
std::string Print(Node &n)
{
int i = 0, k = n.Leaf.size();
std::string res = "<n>";
for (; i < k; ++i)
res += n.Leaf[i]->Print(*this);
res += "</n>";
return res;
}
std::string Print(NodeA &n)
{
int i = 0, k = n.Leaf.size();
std::string res = "<A>";
for (; i < k; ++i)
res += n.Leaf[i]->Print(*this);
res += "</A>";
return res;
}
};
int main(int argc, const char *argv[])
{
NodeA tree;
tree.Add(new NodeA).Add(new NodeA);
Printer p;
std::cout << tree.Print(p) << std::endl;
return 0;
}