Reputation: 16449
I want to clone a vector of unique pointers using RTTI.
Currently, there is an abstract base class, Node
, and derived classes, Element
and TextNode
. Element
contains a vector of unique Node
pointers.
I'm able to create an object of type Element
and move it into the vector. I want to be able to clone an Element
and push a copy into the vector, but I'm struggling with the copy constructor of Element
.
Is this possible? How can I clone a unique pointer using RTTI? Is there a better approach for this problem?
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Node {
virtual ~Node() = default;
virtual std::string toString() const = 0;
};
struct Element : Node {
Element() = default;
Element(const Element &element) {
// clone children
// for (const auto &child : element.children) children.push_back(std::make_unique</* get RTTI */>(child));
}
Element(Element &&) = default;
std::string toString() const override {
std::string str = "<Node>";
for (const auto &child : children) str += child->toString();
str += "</Node>";
return str;
}
std::vector<std::unique_ptr<Node>> children;
};
struct TextNode : Node {
std::string toString() const override { return "TextNode"; }
};
int main() {
Element root;
Element node;
node.children.push_back(std::make_unique<TextNode>());
// This copy doesn't work because I don't know how to implement the copy constructor
root.children.push_back(std::make_unique<Element>(node));
root.children.push_back(std::make_unique<Element>(std::move(node)));
root.children.push_back(std::make_unique<TextNode>());
std::cout << root.toString();
}
Actual output:
<Node><Node></Node><Node>TextNode</Node>TextNode</Node>
Expected output:
<Node><Node>TextNode</Node><Node>TextNode</Node>TextNode</Node>
Upvotes: 1
Views: 66
Reputation: 596407
What you are asking for can't be done directly with RTTI, but it can be done indirectly by checking the class types manually. Since you have only a small number of classes to check, you can do that like this:
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Node {
virtual ~Node() = default;
virtual std::string toString() const = 0;
};
struct Element : Node {
Element() = default;
Element(const Element &element) {
// clone children
for (const auto &child : element.children) {
Node *n = child.get();
if (Element *e = dynamic_cast<Element*>(n)) {
children.push_back(std::make_unique<Element>(*e));
}
else if (TextNode *t = dynamic_cast<TextNode*>(n)) {
children.push_back(std::make_unique<TextNode>(*t));
}
}
}
Element(Element &&) = default;
std::string toString() const override {
std::string str = "<Node>";
for (const auto &child : children)
str += child->toString();
str += "</Node>";
return str;
}
std::vector<std::unique_ptr<Node>> children;
};
struct TextNode : Node {
std::string toString() const override { return "TextNode"; }
};
int main() {
Element root;
Element node;
node.children.push_back(std::make_unique<TextNode>());
root.children.push_back(std::make_unique<Element>(node));
root.children.push_back(std::make_unique<Element>(std::move(node)));
root.children.push_back(std::make_unique<TextNode>());
std::cout << root.toString();
}
But, needless to say, this will get tedious and error-prone if you add more descendant classes later.
A better option is to add a virtual clone()
method to Node
, and then have Element
and TextNode
(and future descendants) override it to make copies of themselves, eg:
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Node {
virtual ~Node() = default;
virtual std::string toString() const = 0;
virtual std::unique_ptr<Node> clone() const = 0;
};
struct Element : Node {
Element() = default;
Element(const Element &element) {
// clone children
for (const auto &child : element.children)
children.push_back(child->clone());
}
Element(Element &&) = default;
std::string toString() const override {
std::string str = "<Node>";
for (const auto &child : children)
str += child->toString();
str += "</Node>";
return str;
}
std::unique_ptr<Node> clone() const override {
return std::make_unique<Element>(*this);
}
std::vector<std::unique_ptr<Node>> children;
};
struct TextNode : Node {
std::string toString() const override { return "TextNode"; }
std::unique_ptr<Node> clone() const override {
return std::make_unique<TextNode>(*this);
}
};
int main() {
Element root;
Element node;
node.children.push_back(std::make_unique<TextNode>());
root.children.push_back(std::make_unique<Element>(node));
root.children.push_back(std::make_unique<Element>(std::move(node)));
root.children.push_back(std::make_unique<TextNode>());
std::cout << root.toString();
}
Upvotes: 2