I'm working on implementing a user-defined object (Patient) container Family_tree but I'm struggling to code the insert and related functions so that any changes made to any of the nodes actually persist in the tree.
What I'm aiming for is the possibility for the user interacting with the GUI to select a Patient and simply click on a "Add spouse" or "Add child" button for example and have the corresponding slot call the insert function with the two Patient(s) of interest and the relationship between them. The current insert obviously doesn't work (which is why it's commented out) but I wanted to explain how it should work in my head: the insert has insertHelper either find a node already in the tree and return it or create a new node if necessary and then calls insertNodes to actually adjust the relationship between the two nodes based on the third parameter.
I'm trying to use nodes*& references because any changes made to the nodes don't persist since I'm creating them as local variables despite having a Family_tree member be part of the PatientRelatives (and even the MainWindow) class in the GUI. I'm very much new to all this so I would truly appreciate it if someone could help me out on this. Thank you for your time!
//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 (this is part of the 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
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;
}
}
It's a lot of code to absorb, but I think a major problem is here
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) {}
};
Your nodes are holding references to patients, but they should be holding actual patients. In other words
Patient& p;
should be
Patient p;
If you hold a reference then there is the danger that the referred to object might no longer exist. This is called a dangling reference and will often cause your code to crash. Since it seems you are creating your patients as local objects, e.g.
void PatientRelatives::addChild() {
Patient patient;
this is going to be a problem. The child patent will no longer exist once the PatientRelatives::addChild
method exits, and your node will be left with a dangling reference.
Change your node
class to hold actual patients instead of references to patients and this particalar problem will go away.
However this does require your Patient
class to be copyable, and since you haven't shown that I can't say whether that is true. If it is not then that is another modification you need to make.
The node pointer references node*&
in Family_tree::insertNodes
appear to be unnecessary since you are not assigning to the node variables themselves, only to what they point to. You should remove the reference.