Baumstruktur - this-Wert zuweisen Alternative
-
Hallo,
ich habe, in Anlehnung an das Tutorial hier aus dem Forum, folgenden Code für dynamische Gleichungen:
Vorab: Rohe Pointer und
new
sind nur der Einfachheit halber für Demonstrationszwecke... ich weiß dass das so nicht gemacht werden sollte.#include <iostream> struct Node { virtual double eval() const = 0; }; struct AddNode : Node { Node *lhs, *rhs; AddNode(Node *lhs, Node *rhs) : lhs(lhs), rhs(rhs) {} virtual double eval() const { return lhs->eval() + rhs->eval(); } }; struct SubNode : Node { Node *lhs, *rhs; SubNode(Node *lhs, Node *rhs) : lhs(lhs), rhs(rhs) {} virtual double eval() const { return lhs->eval() - rhs->eval(); } }; struct ValNode : Node { double value; ValNode(double value) : value(value) {} virtual double eval() const { return value; } }; int main() { Node *expression = new AddNode(new ValNode(5), new ValNode(2)); std::cout << expression->eval() << "\n"; }
Der Code funktioniert soweit: Ich kann Gleichungen erstellen und auswerten.
Jetzt würde ich aber gerne eine Funktion hinzufügen, die z.B. alle "
+
" in einer Gleichung durch "-
" ersetzt - also alleAddNode
in einer Gleichung sollen durchSubNode
ersetzt werden und zwar möglichst ohne immer den Ganzen Baum kopieren zu müssen. Ich wollte das zuerst wie dieeval
Funktion implementieren, also als virtuelle, vererbte Member-Funktion.Problem: Dafür müsste ich ja dem
this
-Objekt in der Member-Funktion einen neuen Wert zuweisen, was ja nicht möglich ist (dathis
const
ist):virtual void replace() { this = new SubNode(lhs, rhs); } // In AddNode, geht aber nicht
Wie würde man das gut lösen?
-
Über das Parent Objekt.
-
Mechanics schrieb:
Über das Parent Objekt.
Hm, der
AddNode
hat hier aber gar kein Parent Objekt (AddNode
ist in dem Beispiel ja selbst der Root-Knoten)...Und selbst wenn ich dann noch eine Art
ExprNode
als Zwischenschicht einführe, brauche ich doch danndynamic_cast
, um auf dielhs
undrhs
zugreifen zu können? Das würde ich eigentlich gerne vermeiden...
-
Du willst nicht this austauschen. Du willst dem Node drüber sagen, dass sein lhs jetzt was anderes ist.
Kann man vielleicht über einen Visitor machen.
-
Mechanics schrieb:
Du willst nicht this austauschen. Du willst dem Node drüber sagen, dass sein lhs jetzt was anderes ist.
Kann man vielleicht über einen Visitor machen.Und wie?
Habe das jetzt so versucht umzusetzen:
#include <iostream> struct ExprNode; struct AddNode; struct SubNode; struct ValNode; struct Visitor { virtual void visit(ExprNode &n) = 0; virtual void visit(AddNode &n) = 0; virtual void visit(SubNode &n) = 0; virtual void visit(ValNode &n) = 0; }; enum class NodeType { Expr, Add, Sub, Val }; struct Node { virtual double eval() const = 0; virtual void accept(Visitor &v) = 0; virtual NodeType type() const = 0; }; struct ExprNode : Node { Node *expr; ExprNode(Node *expr) : expr(expr) {} virtual double eval() const { return expr->eval(); } virtual void accept(Visitor &v) { v.visit(*this); } virtual NodeType type() const { return NodeType::Expr; } }; struct AddNode : Node { Node *lhs, *rhs; AddNode(Node *lhs, Node *rhs) : lhs(lhs), rhs(rhs) {} virtual double eval() const { return lhs->eval() + rhs->eval(); } virtual void accept(Visitor &v) { v.visit(*this); } virtual NodeType type() const { return NodeType::Add; } }; struct SubNode : Node { Node *lhs, *rhs; SubNode(Node *lhs, Node *rhs) : lhs(lhs), rhs(rhs) {} virtual double eval() const { return lhs->eval() - rhs->eval(); } virtual void accept(Visitor &v) { v.visit(*this); } virtual NodeType type() const { return NodeType::Sub; } }; struct ValNode : Node { double value; ValNode(double value) : value(value) {} virtual double eval() const { return value; } virtual void accept(Visitor &v) { v.visit(*this); } virtual NodeType type() const { return NodeType::Val; } }; struct ReplaceVisitor : Visitor { virtual void visit(ExprNode &n) { if (n.expr->type() == NodeType::Add) { /* Wie komme ich hier jetzt an n.expr->lhs ohne dynamic_cast? */ } } virtual void visit(AddNode &n) { /* Selbe hier */ } virtual void visit(SubNode &n) { /* Und hier */ } virtual void visit(ValNode &n) {} }; int main() { Node *expression = new ExprNode(new AddNode(new ValNode(5), new ValNode(2))); std::cout << expression->eval() << "\n"; ReplaceVisitor v; expression->accept(v); std::cout << expression->eval() << "\n"; }
Problem: Auch mit Visitor komme ich nicht an die benötigte Information ran (siehe Kommentar im Code)...
Muss man hier in jedem Objekt auch noch sein Parent speichern? Das will ich vermeiden, weil man dann die Gleichungen ja nicht mehr so einfach hinschreiben kann wie oben in Zeile 68, (Im fertigen Code dann mit Operator-Überladung), zumindest sehe ich nicht, wie.
-
Die visit Funktionen könnten einen Node zurückgeben.
virtual Node visit(const AddNode &n);
-
Mechanics schrieb:
Die visit Funktionen könnten einen Node zurückgeben.
virtual Node visit(const AddNode &n);
Verstehe ich nicht...
Wenn ich sowas habe:
struct AddNode : Node { // ... virtual void accept(Visitor &v) { auto *tmp = v.visit(*this); // Was jetzt tun mit tmp? } };
Inwiefern hilft mir dass dann bzw. was soll ich jetzt mit
tmp
anfangen?
-
Das kriegst schon irgendwie hin Die accept Funktion muss nichts mit dem Rückgabewert anfangen können, der Visitor wird ja auch selber irgendwie aufgerufen, bevor er accept aufruft. Oder, wenn accept von außen aufgerufen wird, dann kann das eben auch den Node zurückgeben.
-
Hallo happystudent
Ich hatte 100% genau dasselbe Problem, als ich mit meinem CAS begonnen habe.
Meine Lösung ist sehr ähnlich, wie die von Mechanics beschriebene. Dieaccept
-Memberfunktionen können wahlweise einenunique_ptr
zurückgeben. Um diesen Rückgabewert zu verwenden, habe ich eine globale Funktion geschrieben:template< typename T > void Apply( std::unique_ptr< T >& Node, BasicMutableVisitor& Visitor ) { if( auto New = Node->Apply( Visitor ) ) { // oder Node->accept in deinem Fall if( !IsType< T >( New.get() ) ) throw std::logic_error{ "Apply: Visitor has returned an incompatible type" }; Node = StaticUniquePtrCast< T >( std::move( New ) ); } }
Grundsätzlich wird der Visitor auf den Node angewandt und der Rückgabewert wird, falls vorhanden, in den Node hineingemoved. Bei rekursiven Visitors schreibst du in die
accept
-Funktion nochApply( Lhs )
etc. hinein. In meinem Projekt habe ich eine Hierarchie von Visitors, die wahlweise shallow/recursive, mutable/immutable, head-first/tail-first sein können.Zu deiner anderen Frage "Wie komme ich hier jetzt an n.expr->lhs ohne dynamic_cast?": Wieso nicht einfach
static_cast
? Wenn du mit einemenum
-Flag schon sichergestellt hast, dass ein Pointer auf einen bestimmten Typ zeigt, dann kannst du dir sicher sein, dass das kein UB erzeugt.LG
-
Fytch schrieb:
Hallo happystudent
Ich hatte 100% genau dasselbe Problem, als ich mit meinem CAS begonnen habe.
Meine Lösung ist sehr ähnlich, wie die von Mechanics beschriebene.Dachte ich mir doch dass noch mehr Leute dieses Problem haben werden
Habe es inzwischen hinbekommen, hier meine Lösung. Falls jemand noch Verbesserungsvorschläge (außer rohe Pointer/new) auffallen, gerne her damit.
Danke @Fytch und @Mechanics.
#include <iostream> #include <functional> struct Node; struct RootNode; struct AddNode; struct SubNode; struct ValNode; struct Visitor { virtual Node *visit(RootNode &n) = 0; virtual Node *visit(AddNode &n) = 0; virtual Node *visit(SubNode &n) = 0; virtual Node *visit(ValNode &n) = 0; }; enum class NodeType { Root, Add, Sub, Val }; struct Node { virtual double eval() const = 0; virtual Node *accept(Visitor &v) = 0; virtual NodeType type() const = 0; virtual void iterate_dfs(std::function<void(Node*&)>) = 0; }; struct RootNode : Node { Node *expr; RootNode(Node *expr) : expr(expr) {} virtual double eval() const { return expr->eval(); } virtual Node *accept(Visitor &v) { return v.visit(*this); } virtual NodeType type() const { return NodeType::Root; } virtual void iterate_dfs(std::function<void(Node*&)> f) { f(expr); expr->iterate_dfs(f); } }; struct AddNode : Node { Node *lhs, *rhs; AddNode(Node *lhs, Node *rhs) : lhs(lhs), rhs(rhs) {} virtual double eval() const { return lhs->eval() + rhs->eval(); } virtual Node *accept(Visitor &v) { return v.visit(*this); } virtual NodeType type() const { return NodeType::Add; } virtual void iterate_dfs(std::function<void(Node*&)> f) { f(lhs); lhs->iterate_dfs(f); f(rhs); rhs->iterate_dfs(f); } }; struct SubNode : Node { Node *lhs, *rhs; SubNode(Node *lhs, Node *rhs) : lhs(lhs), rhs(rhs) {} virtual double eval() const { return lhs->eval() - rhs->eval(); } virtual Node *accept(Visitor &v) { return v.visit(*this); } virtual NodeType type() const { return NodeType::Sub; } virtual void iterate_dfs(std::function<void(Node*&)> f) { f(lhs); lhs->iterate_dfs(f); f(rhs); rhs->iterate_dfs(f); } }; struct ValNode : Node { double value; ValNode(double value) : value(value) {} virtual double eval() const { return value; } virtual Node *accept(Visitor &v) { return v.visit(*this); } virtual NodeType type() const { return NodeType::Val; } virtual void iterate_dfs(std::function<void(Node*&)> f) {} }; template <typename ReplaceNode> struct ReplaceVisitor : Visitor { virtual Node *visit(RootNode &n) { return nullptr; } virtual Node *visit(AddNode &n) { return new ReplaceNode(n.lhs, n.rhs); } virtual Node *visit(SubNode &n) { return nullptr; } virtual Node *visit(ValNode &n) { return nullptr; } }; int main() { Node *expression = new RootNode(new AddNode(new ValNode(5), new ValNode(2))); std::cout << expression->eval() << "\n"; ReplaceVisitor<SubNode> v; expression->iterate_dfs([&v](Node *&node) { if (auto *tmp = node->accept(v)) { node = tmp; } }); std::cout << expression->eval() << "\n"; }
-
Ich würde die Memberfunktionen von
Visitor
nicht rein virtuell machen, sondern alle implementieren und einfachnullptr
zurückgeben lassen.LG