Zeiger in Klassen



  • Hallo zusammen,

    ich versuche gerade einen graphentheoretischen Algorithmus zu implementieren. Die Knoten und Kanten sollen als Klassen definiert werden, die als Member im geschützten Bereich wiederum Zeiger auf Knoten und Kanten enthalten. Mit Memberfunktionen im öffentlichen Bereich soll man darauf zugreifen können. Das Ganze sind dann etwa so aus:

    class edge
    {
    private:
    vertex *source;
    ...
    public:
    ...
    void set_source(vertex v) {source = &v;}
    vertex get_source() {return *source;}
    ...
    };

    class vertex
    {
    private:
    int distance;
    ...
    public:
    ...
    void set_distance(int d) {distance = d;}
    int get_distance() {return distance;}
    ...
    };

    In der main-Funktion habe ich dann stehen:
    ...
    vertex v1;
    edge e1;
    ...
    v1.set_distance(5);
    e1.set_source(v1);
    ...

    Ich möchte also v1 als Ursprung von e1 setzen. Eigentlich würde ich jetzt erwarten, dass e1.get_source() die gleichen Werte hat wie v1, z.B.
    e1.get_source().get_distance() == v1.get_distance() == 5.
    Aber das ist gerade nicht der Fall.
    Wo ist mein Fehler und wie kann ich ihn beheben?



  • Was macht das?

    void set_source(vertex v) {source = &v;}



  • Mit der Zeile
    void set_source(vertex v) {source = &v;}
    ist eine Funktion gemeint, die als Argument ein Objekt der Klasse vertex enthält und eigentlich unter source dieses Objekt speichern soll, bzw. weil source ja nur ein Zeiger ist, die Adresse von dem Objekt v der Klasse vertex.

    Hätte ich source nicht als pointer definiert, sondern als Objekt der Klasse vertex
    würde der Code entsprechend
    ...
    vertex source
    ...
    void set_source(vertex v) {source = v;}
    ...
    lauten und diesem Fall würde alles so laufen, wie ich mir das vorstelle, d.h. e1.get_source() wäre eine Kopie von v1.

    Wahrscheinlich habe ich bei der Definition von set_source einen Fehler gemacht.



  • kuchenwurst schrieb:

    Mit der Zeile
    void set_source(vertex v) {source = &v;}
    ist eine Funktion gemeint, die als Argument ein Objekt der Klasse vertex enthält und eigentlich unter source dieses Objekt speichern soll, bzw. weil source ja nur ein Zeiger ist, die Adresse von dem Objekt v der Klasse vertex.

    Richtig, es wird die Adresse von dem Objekt v der Klasse vertex in source gespeichert. Das ist aber nur eine Kopie von dem Vertex aus der main! Du speicherst einen Zeiger auf eine lokale Variable innerhalb der Funktion set_source. Es ist absolut undefiniertes Verhalten, was ein späterer Zugriff macht, da der Speicherbereich der lokalen Variablen ja danach wieder freigegeben wird.



  • kuchenwurst schrieb:

    void set_source(vertex v) {source = &v;}
    

    Wahrscheinlich will manni darauf hinaus, dass der Parameter v by value entgegengenommen wird, und es sich bei v nicht um die Instanz v1 handelt, die du in der main-Funtklion übergibst, sondern um eine Kopie davon, die nur innerhalb der Methode set_source existiert (sobald set_source verlassen wird, wird v zerstört, indem der Destruktor für v aufgerufen, und der belegte Speicher - hier auf dem Stack - freigegeben wird).

    Der Pointer, den du source zuweist, zeigt also auf ein zerstörtes Objekt, sobald set_source verlassen wird.

    Du hast mehrere Möglichkeiten das Problem zu lösen:

    1. Wie schon von dir erwähnt, kannst du source nicht als Pointer, sondern direkt als vertex-Member deklarieren. Das ist aber wahrscheinlich nicht das was du möchtest, da dann jede edge ihre eigene Kopie von source hat und es in deinem Graphen sicher auch mehrere Kanten geben soll, die sich einen Knoten teilen (d.h. wenn du den Knoten z.B. verschiebst, sollen sich sicher auch alle Kanten, die diesen Knoten enthalten mitverschieben).

    2. Du änderst den Parameter von set_source so dass entwender eine Referenz

    void set_source(vertex& v) {source = &v;}
    

    oder ein Pointer

    void set_source(vertex* v) {source = v;}
    

    übergeben wird. Das hat allerdings den Nachteil, dass source nur so lange gültig ist, wie die Instanz v1 , die du in der main-Funktion übergeben hast, existiert:

    int main()
    {
        edge e1;
    
        {
            vertex v1;
            v1.set_distance(5);
            e1.set_source(v1);
            e1.get_source().get_distance(); // Solange v1 existiert, gibt get_source einen gültigen Pointer zurück.
        }
    
        e1.get_source().get_distance(); // BÄM! v1 existiert nicht mehr.
    }
    

    Das kann man so machen, wenn die Knoten irgendwo zentral verwaltet werden (z.b. in einer graph-Klasse), und sichergestellt ist, das alle Knoten so lange existieren, wie es Kanten gibt, die darauf verweisen.

    3. Wenn du deinen Graphen kreuz- und quer verknüpfen willst oder Knoten sogar in mehreren Graphen vorkommen können, macht es hier eventuell Sinn mit Shared Pointern zu arbeiten ( std::shared_ptr ):

    #include <memory>
    
    class edge
    {
        private:
            std::shared_ptr<vertex> source;
        public:
            void set_source(const std::shared_ptr<vertex>& v) { source = v; }
            std::shared_ptr<vertex> get_source() { return source; }
    };
    
    int main()
    {
        edge e1;
    
        {
            std::shared_ptr<vertex> v1(new vertex());
            v1->set_distance(5);
            e1.set_source(v1);
            e1.get_source()->get_distance();
        }
    
        e1.get_source()->get_distance(); // Kein Problem dank shared_ptr
        // Aber Vorsicht! Wenn source nicht gesetzt, dann gibt get_source() einen leeren shared_ptr zurück (e1.get_source().get() == nullptr).
        // In diesem Fall würde hier ein null-Pointer dereferenziert. Also vorher prüfen, oder anderweitig sicherstellen,
        // dass das nicht passieren kann (z.B. source im Konstruktor von edge auf "new vertex()" setzen).
    }
    

    Diese stellen sicher, dass die vertex -Instanz so lange existiert, wie es mindestens einen shared_ptr gibt, der darauf verweist.

    Gruss,
    Finnegan



  • Super, viel Dank. Ich hab jetzt verstanden, wo mein Fehler gewesen ist.


Anmelden zum Antworten