我正在实现一个在解析器中使用的简单Tree类,我需要遍历解析树的结构并逐步构建它。
这是演示该问题的类的精简版本(可在this repl.it session中执行):
template <typename T>
class Tree {
T val;
std::deque<Tree<T>> children;
public:
Tree() {}
Tree(T value) : val(value) {}
Tree(T value, std::deque<Tree<T>> children) : val(value), children(children) {}
std::deque<Tree<T>> getChildren() { return this->children; }
void appendChild(T value) {
this->children.push_back(value);
// this->children.emplace_back(value);
std::cout << "Appended child to node with value " << this->val << ".\n";
printChildren();
}
void printChildren() {
std::cout << "children for " << this << "(" << this->val << ")"
<< ": { ";
for (auto &child : this->children) {
std::cout << &child << "(" << child.val << ") ";
}
std::cout << "}\n";
}
};
对于每个节点,子项存储在std::deque
中,因此可以将子项添加到任一端。在测试我的类时,我发现我不能依赖于通过逐步构建树而不是使用初始化列表一次性构建树所产生的结构。
这里有一些代码来练习课程并展示正在发生的事情:
std::cout << "Constructing Tree\n\n";
Tree<int> t(1);
t.appendChild(2);
t.getChildren()[0].appendChild(3);
std::cout << "\n\nPrinting tree from main\n\n";
t.printChildren();
t.getChildren()[0].printChildren();
这有以下输出:
Constructing Tree
Appended child to node with value 1.
children for 0x7ffe9fd41820(1): { 0xb69080(2) }
Appended child to node with value 2.
children for 0xb694a0(2): { 0xb696b0(3) }
Printing tree from main
children for 0x7ffe9fd41820(1): { 0xb69080(2) }
children for 0xb698c0(2): { }
如您所见,每次打印时,值为2
的节点的地址都不同。当它首次附加到1
节点时,它的地址为0xb69080
。在获得自己的孩子后,它的地址为0xb694a0
。然后,当它从main
函数访问时,它的地址为0xb698c0
。
另外,似乎当它被移动时它会以某种方式失去它的孩子。最后一行应显示2
节点有一个值为3
的子节点。
这里发生了什么?
答案 0 :(得分:4)
我想你的问题就在这里
std::deque<Tree<T>> getChildren() { return this->children; }
getChildren()
返回副本的孩子。
尝试
std::deque<Tree<T>> & getChildren() { return this->children; }
// .................^
返回对内部children
的引用,如果要使用返回值修改它。
我的意思是:如果getChildren()
返回children
的副本,则带有
t.getChildren()[0].appendChild(3);
您将值3
的孩子附加到children
返回的getChildren()
副本的第一个元素。
此副本未被保存,因此它是一个临时值,在丢失附加的3
子项后会立即销毁。