Klasse in Klasse verwenden



  • Hi 🙂

    Heute habe ich mich mal an meinem ersten C++-Programm versucht 🙂

    Leider habe ich noch ein Problem, wenn ich in einer Klasse diese Klasse auch als Eigenschaft angegeben will. Wie muss ich denn das machen?

    Hier mein Versuch:

    #ifndef EDGE_H
    #define EDGE_H
    
    #include "Face.h"
    #include "Vertex.h"
    
    class Edge
    {
        public:
            Edge(Vertex, Vertex);
            void setLeft(Face, Edge, Edge);
            void setRight(Face, Edge, Edge);
            Face getLeftFace();
            Face getRightFace();
            Edge getLeftSuccessorEdge();
            Edge getRightSuccessorEdge();
            Edge getLeftPredecessorEdge();
            Edge getRightPredecessorEdge();
        private:
            Vertex startPoint;
            Vertex endPoint;
            Face leftFace;
            Face rightFace;
            Edge leftSuccessorEdge;
            Edge rightSuccessorEdge;
            Edge leftPredecessorEdge;
            Edge rightPredecessorEdge;
    };
    
    #endif // EDGE_H
    
    #include "Edge.h"
    
    Edge::Edge(Vertex _startPoint, Vertex _endPoint)
    {
        startPoint = _startPoint;
        endPoint = _endPoint;
    }
    void Edge::setLeft(Face _face, Edge _successorEdge, Edge _predecessorEdge)
    {
        leftFace = _face;
        leftSuccessorEdge = _successorEdge;
        leftPredecessorEdge = _predecessorEdge;
    }
    void Edge::setRight(Face _face, Edge _successorEdge, Edge _predecessorEdge)
    {
        rightFace = _face;
        rightSuccessorEdge = _successorEdge;
        rightPredecessorEdge = _predecessorEdge;
    }
    Face Edge::getLeftFace()
    {
        return leftFace;
    }
    Face Edge::getRightFace()
    {
        return rightFace;
    }
    Edge Edge::getLeftSuccessorEdge()
    {
        return leftSuccessorEdge;
    }
    Edge Edge::getRightSuccessorEdge()
    {
        return rightSuccessorEdge;
    }
    Edge Edge::getLeftPredecessorEdge()
    {
        return leftPredecessorEdge;
    }
    Edge Edge::getRightPredecessorEdge()
    {
        return rightPredecessorEdge;
    }
    
    #include <iostream>
    #include <cstdlib>
    #include <exception>
    #include <stdexcept>
    #include "Edge.h"
    #include "Face.h"
    #include "Vertex.h"
    
    using namespace std;
    
    void terminateHandler()
    {
        // Exceptionhandling nicht korrekt abgelaufen
        cerr << "Exception" << endl;
        abort();
    }
    int main(int argc, char *argv[])
    {
    
        try
        {
            set_terminate(terminateHandler);
    
            Vertex leftSuccessorStartPoint(100.0, 200.0, 300.0);
            Vertex leftSeccessorEndPoint(101.0, 201.0, 301.0);
            Vertex leftPredecessorStartPoint(400.0, 500.0, 600.0);
            Vertex leftPredecessorEndPoint(501.0, 501.0, 601.0);
            Vertex rightSuccessorStartPoint(110.0, 210.0, 310.0);
            Vertex rightSeccessorEndPoint(111.0, 211.0, 311.0);
            Vertex rightPredecessorStartPoint(410.0, 510.0, 610.0);
            Vertex rightPredecessorEndPoint(511.0, 511.0, 611.0);
    
            Edge leftSuccessorEdge(leftSuccessorStartPoint, leftSeccessorEndPoint);
            Edge leftPredecessorEdge(leftPredecessorStartPoint, leftPredecessorEndPoint);
            Edge rightSuccessorEdge(rightSuccessorStartPoint, rightSeccessorEndPoint);
            Edge rightPredecessorEdge(rightPredecessorStartPoint, rightPredecessorEndPoint);
    
            Face leftFace;
            Face rightFace;
    
            Vertex startPoint(1, 2, 3);
            Vertex endPoint(4, 5, 6);
    
            Edge edge(startPoint, endPoint);
            edge.setLeft(leftFace, leftSuccessorEdge, leftPredecessorEdge);
            edge.setRight(rightFace, rightSuccessorEdge, rightPredecessorEdge);
        }
        catch(const string what)
        {
            cerr << "Exception: " << what << endl;
            return EXIT_FAILURE;
        }
        catch(bad_exception& exc)
        {
            // Fehlerbehandlung liefert selbst einen Fehler
            cerr << "Exception: " << exc.what() << endl;
            return EXIT_FAILURE;
        }
        catch(exception& exc)
        {
            // Fehler im Programm
            cerr << "Exception: " << exc.what() << endl;
            return EXIT_FAILURE;
        }
        return EXIT_SUCCESS;
    }
    

    Fehlermeldungen:

    ||=== Build: Debug in a3dds (compiler: GNU GCC Compiler) ===|
    include\Edge.h|24|error: field 'leftSuccessorEdge' has incomplete type|
    include\Edge.h|25|error: field 'rightSuccessorEdge' has incomplete type|
    include\Edge.h|26|error: field 'leftPredecessorEdge' has incomplete type|
    include\Edge.h|27|error: field 'rightPredecessorEdge' has incomplete type|
    ||=== Build failed: 4 error(s), 0 warning(s) (0 minute(s), 22 second(s)) ===|

    Habe im Internet gelesen, dass man noch ein * dazumachen muss, aber das liefert mir auch wieder nur Fehler. Wie geht es denn richtig?


  • Mod

    Wie soll das denn gehen? Eine edge enthält eine edge, enthält eine edge, enthält eine edge, usw?

    Da stimmt was nicht im Design. Der Tipp, das stattdessen mit Zeigern zu machen, wäre durchaus eine Lösung. Aber falls du nicht weißt, was ein Zeiger ist, solltest du da auf keinen Fall anfangen, wild Sternchen zu verteilen, bis es compiliert.
    Vom Rest deines Programms her vermute ich mal, dass du weder von Referenzen, noch von Zeigern, jemals etwas gehört hast, oder? Falls dies der Fall ist, lern erst einmal ein bisschen weiter C++, bis du Referenzen und Zeiger gelernt hast, dann wird dir das Problem und die Lösung ganz von alleine klar. Das Thema Referenzen/Zeiger ist jedoch zu komplex, um im Rahmen dieses Forums erklärt zu werden.



  • Ich weiß schon grundsätzlich, was ein Zeiger ist. Und die Datenstruktur stimmt, das ist nichts, was ich selbst erfunden habe.

    Wenn du mir hilfst, das Problem zu lösen, kläre ich dich über diese Datenstruktur auf 😉



  • Nein, sie stimmt so nicht.

    Wenn das z.B. aus Java übernommen hast, wäre das nicht das gleiche.

    Wie SeppJ schon sagte: Du brauchst eine Art von Referenzierung (Referenz oder (Smart-)Zeiger) auf die Kinder-Edges. Ansonsten hast du einen zyklus der nicht aufgebrochen werden kann, weder vom Programmier noch vom Compiler. Die Folge wäre ein unendlich großes Objekt, was ja offensichtlich nicht existieren kann.

    Soll das irgendwas Baumartiges sein ? (Quadtree, Octree, Neuronales Netz?)



  • Soll das irgendwas Baumartiges sein ? (Quadtree, Octree, Neuronales Netz?)

    Diese Datenstruktur nennt sich "winged-edge". Das ist eine Alternative zu B-Reps. Die werden für 3D-Programme verwendet.

    Du brauchst eine Art von Referenzierung

    Ja, das habe ich auch schon kapiert. C++ kann das nicht kompilieren, weil es beim Kompilieren der Klasse die Klasse halt noch nicht kennt.

    Naja, ich werds halt alleine versuchen, das irgendwie mit Zeiger hinzubekommen. Vielleicht braucht ich die auch gar nicht, sondern kann mit Vector arbeiten ...



  • tröröö schrieb:

    Vielleicht braucht ich die auch gar nicht, sondern kann mit Vector arbeiten ...

    Da bist du vermutlich irgendwo auf den Tipp gestoßen, ein C-Array ("irgendwas mit Sternchen") durch einen vector zu ersetzen.

    Das hat mit deinem Problem nichts zu tun.



  • Naja, theoretisch ginge das schon mit einem Vector. Aber die semantische Zuordnung (alá LeftEdge, RightEdge oder wie das war) wäre nicht mehr gegeben.

    Für mich sieht das so auf den ersten Blick schon nach einer Struktur aus, die man mit Zeigern umsetzt. Aber da müsste tröörö sich halt um den Speicher kümmern.

    (Ich will an der Stelle jetzt gar nichts für oder gegen Smart-Pointer sagen. Die sind ja in letzter Zeit hier im Forum gerade bei Baumstrukturen mal geliebt mal total verpöhnt.)



  • Skym0sh0 schrieb:

    Naja, theoretisch ginge das schon mit einem Vector. Aber die semantische Zuordnung (alá LeftEdge, RightEdge oder wie das war) wäre nicht mehr gegeben.

    Nein, sowas geht nicht:

    class Edge
    {
      std::vector<Edge> edges;
    }
    

    Sein eigentliches Problem ist doch, dass die Klasse "Edge" Member der Klasse "Edge" hält. Egal, ob diese individuelle Member sind oder in einem vector gehalten werden.

    Das lässt sich nur durch Verwendung von Referenzen/Pointern aufbrechen. Natürlich *könnte* man diese Pointer dann in einem vector halten, aber das ist jetzt erstmal wurscht 😉



  • Warte, jetzt bring mich nicht in Bedrängnis. Das geht echt nicht mit dem Vector??!
    Das probier ich aus... 😕 😮



  • Skym0sh0 schrieb:

    Warte, jetzt bring mich nicht in Bedrängnis. Das geht echt nicht mit dem Vector??!

    achnee, warte mal, der könnte ja auch leer sein.

    nein, vielleicht gehts doch...



  • Also laut Ideone gehts:

    #include <vector>
    
    class X
    {
    	std::vector<X> vx;
    
    public:
    	X()
    	: vx{{}, {}, {}}
    	{}
    };
    
    int main()
    {
    	X x;
    	return 0;
    }
    

    Und hier ist sogar noch das "Phänomen", dass ein Laufzeitfehler erzeugt wird, weil ein Zyklus bei der Initialisierung stattfindet.

    Aber ist eigentlich auch logisch, weil vector einen Pointer auf T hat und sich um Speicher und alles selbst kümmert.



  • ok, danke für die Klarstellung.

    Aber weiterhin gilt: zwei Instanzen können sich nicht gegenseitig beinhalten.

    Es funktioniert somit zwar für eine Baumstruktur, bei der nur in eine Richtung navigiert werden kann. Sowie aber in beide Richtungen navigiert werden soll oder sich zyklische Strukturen ergeben, ist Ende.



  • Folgende Aussagen nur unter der Prämisse, dass ich das richtig verstehe, was du meinst (nicht dass wir aneinander vorbei reden):

    Doch klar geht das in beide Richtungen.
    In einem Binärbaum z.B. kennt jeder Knoten seinen Elternknoten und auch seine (beiden) Kindknoten.
    Das einzige, wodrauf man aufpassen muss, ist beim Löschen. Nicht dass aus Versehen der ganze Baum gelöscht wird.

    Oder verstehe ich dich da falsch bei den zyklischen Strukturen?



  • Skym0sh0 schrieb:

    Folgende Aussagen nur unter der Prämisse, dass ich das richtig verstehe, was du meinst (nicht dass wir aneinander vorbei reden):

    mir geht es immer noch um die Modellierung *ohne Pointer/Referenzen*.

    Skym0sh0 schrieb:

    Doch klar geht das in beide Richtungen.
    In einem Binärbaum z.B. kennt jeder Knoten seinen Elternknoten und auch seine (beiden) Kindknoten.

    ok, modellieren wir das doch mal:

    A
    B1
    C1 C2

    A kennt B1 (Kind) und C1/C2 kennen B (parent). Wie sollen nun sowohl A als auch C1 und C2 den Knoten B1 halten, dass alle auf dieselbe Instanz zugreifen (ohne Pointer/Referenzen!)?



  • Gar nicht, aber genau das wollte ich ja ausschließen; da haben wir aneinander vorbei geredet.

    Selbst wenn A nur B(Kind) kennen soll, und wir modellieren das ohne Zeiger, geht das schon nicht.

    struct Node
    {
        Node child;
    };
    
    int main()
    {
        Node A;
        Node B;
        A.child = B;
    
        // ...
    }
    

    Wird nicht laufen.
    Wenn das aber verschiedene Typen sind, dann geht das wiederum. Aber auch nur, wie du sagst, in eine Richtung:

    struct NodeB
    {
        //NodeA child;
    };
    
    struct NodeA
    {
        NodeB child;
    };
    
    int main()
    {
        NodeA a;
        NodeB b;
        a.child = b;
    //    b.child = a;
    }
    


  • ok, dann hätten wir das auch geklärt 🙂



  • Es handelt sich hier nicht um eine Baumstruktur, wenn ich mich nicht sehr täusche, sondern eher um eine Art Netz. Insbesondere können (und werden) zyklische Objektverweisgruppen entstehen, indem etwa -- jetzt unter der Annahme, dass das alles auf Zeiger umgestellt wäre -- this->LeftSuccessorEdge()->LeftSuccessorEdge()->LeftSuccessorEdge() == this wird.

    In einer Baumstruktur wären die Besitzverhältnisse mit unique_ptr einfach zu lösen; hier ist es schwieriger. shared_ptr reicht nicht, da der Graph zyklisch ist, und ich sehe keine einfache Möglichkeit, das mit weak_ptr aufzubrechen.

    Ich würde mir wahrscheinlich eine Graphenkopfklasse bauen, die die Edges, Faces und Vertices besitzt und ihre Lebensdauer festlegt, und in den Edges, Faces und Vertices selbst mit nackten Zeigern arbeiten.



  • struct Node
    {
        Node child;
    };
    

    Kann nicht funktionieren. Der Compiler reserviert für die Klasse so viel Speicher, wie für jedes einzelne Element benötigt wird. Hat eine Klasse beispielsweise 2 Member vom Typ int, dann ist die gesamte Klasse eben 2*sizeof(int) groß. Wie groß ist jetzt Node? In dem Beispiel sizeof(Node). Da erkennt man schon die Rekursion der Fragestellung. Man bedenke, dass eine Instanz a einen Member a.child hätte, welches ein Member a.child.child hätte, welches einen Member a.child.child.child hätte...

    Ein Zeiger auf Node wäre ok, da sizeof(Node*) bereits exakt definiert ist. Auch ein std::vector<Node> ist ok, da der vector intern ja auch nur ein Zeiger auf die Elemente hält. Ein Node[6] funktioniert wiederum nicht, da dort wieder das gleiche Problem auftritt.



  • tntnet schrieb:

    Auch ein std::vector<Node> ist ok

    Nein, ist es nicht. Templates in der Standardbibliothek verlangen vollständige Typen als Parameter, ausser es steht explizit was anderes (wie z.B. bei unique_ptr ).



  • Nexus schrieb:

    tntnet schrieb:

    Auch ein std::vector<Node> ist ok

    Nein, ist es nicht. Templates in der Standardbibliothek verlangen vollständige Typen als Parameter, ausser es steht explizit was anderes (wie z.B. bei unique_ptr ).

    Jetzt hast Du mich aber erfolgreich irritiert. Also zumindest bin ich mir unsicher. Hast Du da einen Verweis auf den Standard? 😕

    Am Ende der Klassendeklaration ist die Klasse vollständig bekannt. Der Compiler weiß, ob die Klasse alle Voraussetzungen erfüllt. Daher kann er erst mal seine Arbeit erledigen.

    Und auch wenn die compilierbarkeit mit einem Compiler kein Beweis ist, ist es erst mal ein Hinweis, dass es so sein könnte und näherer Betrachtung Wert ist. Ich habe hier mal ein Beispiel:

    #include <vector>
    
    struct Foo
    {
        std::vector<Foo> childs;
    };
    

    Mit g++ -Wall -pedantic -c foo.cpp übersetzt ergibt keinen Fehler und noch nicht mal eine Warnung. Die gcc-version ist 4.7.2. Ist der Code standardkonform?


Log in to reply