树节点插入与修改:如何确保函数调用间变更持久化?
家族树节点修改无法持久化的问题解决
我正在实现存储自定义Patient对象的Family_tree容器,但编写插入及相关函数时遇到问题——对节点的修改无法在树中持久化。
需求是:GUI用户选中一个Patient后,点击“Add spouse”或“Add child”按钮,对应的槽函数调用insert函数,传入两个目标Patient及二者关系,完成节点关联。
当前设计思路:
insert函数(已注释)通过insertHelper查找树中已存在的节点并返回,若不存在则创建新节点;- 随后调用
insertNodes根据关系参数调整两个节点间的关联。
尝试过用node*&引用,但因为节点是局部变量创建的,即便GUI的PatientRelatives(甚至MainWindow)类持有Family_tree成员,修改还是无法持久化。
相关代码
Family_tree.h
class Family_tree { private: class node { public: Patient& p; node* spouse; node* parent; std::vector<node*> children; mutable bool visited; static Patient default_patient; node() : p(default_patient), spouse(nullptr), parent(nullptr), children(), visited(false) {} node(Patient& ptn, node* s, node* p, std::vector<node*> c) : node(ptn, s, p, c, false) {} node(Patient& ptn, node* s, node* p, std::vector<node*> c, bool v) : p(ptn), spouse(s), parent(p), children(c), visited(v) {} }; node* root; };
Family_tree.cpp
Family_tree::node* Family_tree::find(Family_tree::node* current_node, const Patient& p) const { if (p.valid_Patient()) { if (current_node == nullptr || current_node->visited) {return nullptr;} current_node->visited = true; if (current_node->p == p) {current_node->visited = false; return current_node;} for (node* child : current_node->children) { node* n = find(child, p); if (n != nullptr) {current_node->visited = false; return n;} } current_node->visited = false; return nullptr; } else {std::cerr << "Please input a valid patient." << std::endl; return nullptr;} } Family_tree::node* Family_tree::insertHelper(Patient& p) { node* n = find(root, p); if (n == nullptr) {n = new node(p, nullptr, nullptr, std::vector<node*>());} return n; } void Family_tree::insertNodes(node*& node1, node*& node2, const std::string& relationship) { if (relationship == "spouse") { if (node1->p.get_Sex() != node2->p.get_Sex()) { node1->spouse = node2; node2->spouse = node1; } else {throw UnrealisticGender();} } else if (relationship == "parent") { insertNodes(node2, node1, "child"); } else if (relationship == "child") { if (node2->spouse != nullptr) { std::vector<Patient*> possible_children = node2->p.inheritance(node2->spouse->p); bool realistic_genotype = false; for (Patient* child : possible_children) { if (child->get_Genotype() == node1->p.get_Genotype()) { realistic_genotype = true; break; } } if (realistic_genotype) { node2->children.push_back(node1); node1->parent = node2; check_duplicates(node2); } else {throw UnrealisticGenotype();} } else {throw SpouseMissing();} } else {std::cerr << "Insert was not set up correctly." << std::endl;} } /*void Family_tree::insert(Patient& p1, Patient& p2, const std::string& relationship) { node* node1 = insertHelper(p1); node* node2 = insertHelper(p2); insertNodes(node1, node2, relationship); }*/
PatientRelatives.h(GUI模块)
class PatientRelatives : public QWidget { Q_OBJECT private: Family_tree& family; SelectedPatient& selectedPatient; public: PatientRelatives(Family_tree& family, SelectedPatient& selectedPatient, QWidget* parent = 0); void show(); private slots: void addParents(); void addSpouse(); void addChild(); //void addSibling(); void changeRelatives(); };
PatientRelatives.cpp(GUI模块)
void PatientRelatives::addSpouse() { Patient patient = Patient(); Patient* selected = selectedPatient.getSelectedPatient(); EditDetailsDialog* spouseDialog = new EditDetailsDialog(patient, this); if (spouseDialog->exec() == QDialog::Accepted) { Patient spouse = spouseDialog->getPatient(); try { family.insert(spouse, *selected, "spouse"); } catch(const UnrealisticGender& e) { QMessageBox::critical(this, "Error", e.what()); family.delete_p(spouse); } delete spouseDialog; } } void PatientRelatives::addChild() { Patient patient; Patient* selected = selectedPatient.getSelectedPatient(); EditDetailsDialog* childDialog = new EditDetailsDialog(patient, this); if (childDialog->exec() == QDialog::Accepted) { Patient child = childDialog->getPatient(); try { family.insert(child, *selected, "child"); } catch (const SpouseMissing& e) { QMessageBox::critical(this, "Error", e.what()); addSpouse(); try { family.insert(child, *selected, "child"); } catch (const UnrealisticGenotype& e) { QMessageBox::critical(this, "Error", e.what()); family.delete_p(child); } } catch (const UnrealisticGenotype& e) { QMessageBox::critical(this, "Error", e.what()); family.delete_p(child); } delete childDialog; } }
问题分析与修复方案
核心问题
- 新节点未挂载到树结构:
insertHelper创建新节点后,仅返回指针但未将其关联到家族树的任何现有节点(如根节点、选中节点),导致新节点游离,后续无法被找到,修改自然无法持久化。 Patient引用悬空:node类用Patient& p存储对象,但GUI中创建的Patient是局部对象,函数结束后局部对象销毁,引用变为悬空状态,触发未定义行为。find函数遍历不完整:仅遍历子节点,未处理配偶节点,导致无法找到树中已存在的配偶;同时visited标记逻辑可能因循环引用(如配偶关系)导致遍历中断。
修复步骤
1. 修复节点挂载逻辑
修改insert函数,确保新创建的节点被纳入树结构,处理根节点为空的情况:
void Family_tree::insert(Patient& p1, Patient& p2, const std::string& relationship) { node* node1 = insertHelper(p1); node* node2 = insertHelper(p2); // 树为空时,将第一个节点设为根 if (root == nullptr) { root = node1; } insertNodes(node1, node2, relationship); }
2. 替换Patient引用为值或智能指针
避免悬空引用,将node类中的Patient& p改为值存储或智能指针:
class node { public: Patient p; // 改为值存储,或使用std::shared_ptr<Patient>管理生命周期 node* spouse; node* parent; std::vector<node*> children; mutable bool visited; static Patient default_patient; // 构造函数同步修改 node() : p(default_patient), spouse(nullptr), parent(nullptr), children(), visited(false) {} node(Patient ptn, node* s, node* p, std::vector<node*> c) : node(ptn, s, p, c, false) {} node(Patient ptn, node* s, node* p, std::vector<node*> c, bool v) : p(ptn), spouse(s), parent(p), children(c), visited(v) {} };
3. 完善find函数遍历逻辑
增加配偶节点的遍历,优化visited标记避免循环:
Family_tree::node* Family_tree::find(Family_tree::node* current_node, const Patient& p) const { if (!p.valid_Patient()) { std::cerr << "Please input a valid patient." << std::endl; return nullptr; } if (current_node == nullptr || current_node->visited) { return nullptr; } current_node->visited = true; if (current_node->p == p) { current_node->visited = false; return current_node; } // 遍历子节点 for (node* child : current_node->children) { node* n = find(child, p); if (n != nullptr) { current_node->visited = false; return n; } } // 遍历配偶节点 if (current_node->spouse != nullptr) { node* n = find(current_node->spouse, p); if (n != nullptr) { current_node->visited = false; return n; } } current_node->visited = false; return nullptr; }
4. 调整GUI中Patient对象的生命周期
避免局部对象销毁导致的问题,改用动态分配:
void PatientRelatives::addSpouse() { Patient* patient = new Patient(); Patient* selected = selectedPatient.getSelectedPatient(); EditDetailsDialog* spouseDialog = new EditDetailsDialog(*patient, this); if (spouseDialog->exec() == QDialog::Accepted) { *patient = spouseDialog->getPatient(); try { family.insert(*patient, *selected, "spouse"); } catch(const UnrealisticGender& e) { QMessageBox::critical(this, "Error", e.what()); family.delete_p(*patient); delete patient; } } else { delete patient; // 用户取消时释放内存 } delete spouseDialog; }
内容的提问来源于stack exchange,提问作者Nancy Kalaj
相关产品推荐
相关产品推荐

