Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte



  • Guten Morgen,

    ich versuche mich gerade an einem einfachen Neuronalen Netzwerk.
    Dabei erstelle ich Input und Output Neuronen. Diese verdrahte ich voll miteinander.

    In meiner NeuralNetwork.h habe ich folgendes stehen:

    class NeuralNetwork {
    
    	...
    	std::list<WorkingNeuron> outputNeurons;
    public:
    
    	//Neues output erstellen
    	WorkingNeuron* createNewOutput() {
    		WorkingNeuron* out = new WorkingNeuron();
    		outputNeurons.push_back(*out);
    		return out;
    	}
    	std::list<WorkingNeuron> getOutputNeurons() {
    		return outputNeurons;
    	}
            ...
    
    

    Das WorkingNeuron.h hat eine Liste mit allen Connections gespeichert, die durchiteriert werden und dessen Wert berechnet wird.

    class WorkingNeuron : public Neuron {
    
    	std::list<Connection> connections;
    
    public:
    	float getValue() {
    		float sum = 0;
    
    		for(auto it = connections.begin(); it != connections.end(); ++it) {
    			sum = sum + it->getValue();
    		}
    		return sum;
    	}
    

    Diese Funktion versuche ich zum Testen aus der main aufzurufen:

    int main() {
            ...
    	WorkingNeuron *o1 = net.createNewOutput();
            ...
    	std::list<WorkingNeuron> output = net.getOutputNeurons();
    	for(auto it_o = output.begin(); it_o != output.end(); ++it_o)
    		cout << "output[it]: " << it_o->getValue() << endl;
    
    	cout << "Ausgabeneuron: " << o1->getValue() << endl;
    
    	return 0;
    }
    

    Einmal iteriere ich durch die Liste vom NeuralNetwork, das andere mal greife ich auf das Objekt über meinen Zeiger zu. Nun die Ausgabe:

    output[it]: 4
    Ausgabeneuron: 0
    

    Wieso bekomme ich über den Zeigerzugriff eine 0?
    (Bei der getValue Berechnung wird die connections Liste aufgerufen, welche dann eine size() von 0 hat)

    Ich hoffe, das war verständlich - falls ich etwas undeutlich erklärt, bitte nochmal darauf hinweisen.

    Liebe Grüße
    Markus



  • Kann man so nicht nachvollziehen. Kompilierbares Beispiel wäre gut.

    Aber:

    • Warum zur Hölle überall std::list?! Wir hatten vor wenigen Tagen/Wochen(?) auch schon eine Frage zu NNs, wo std::list verwendet wurde. Dort kam bereits dieselbe Frage.
    • Warum gibst du in getOutputNeurons eine Kopie der Liste zurück? (auch das war vor kurzem im Code der Fall)

    Mein best guess wäre also, dass irgendwo beim Kopieren irgendwas kaputt geht.



  • Die STL arbeitet mit Kopien, sobald du ein Objekt in std::list einfügst speichert die Liste eine Kopie des Objektes. Daher die unterschiedlichen Adressen.
    Mögliche Lösungen:

    • benutze std::unique_ptr und leg die in einem std::vector ab. Wenn es keinen wichtigen Grund für std::list geben sollte ist std::vector der Container der Wahl. Der rohe Zeiger des std::unique_ptr kann dann zur Verwaltung der Verbindungen benutzt werden
    • wenn du mit Kopien arbeitest vergib eindeutige IDs für deinen Neuronen und verwalte die Verbindungen über die IDs. Ist natürlich etwas langsamer, da du immer erst die Verbindung zur ID suchen musst.


  • @wernersbachr
    createNewOutput ist Mist. Du erstellst 2 Neurons (das eine mit dem hässlichen new, das andere als Kopie in in dem Vector (bzw. warum auch immer list)).
    Ich kann kir nicht vorstellen, dass das gewollt ist.



  • Okay, also es liegt wohl am Kopieren.
    ich verwende nun anstatt list einen vector, dabei bleibt das Problem aber bestehen.
    (Eine unschöne Lösung wäre wohl return &(inputNeurons.back()); in createNewInput)

    Ich erstelle die Neuronen mit new, um einen Zeiger zu erhalten, den ich an eine neue Connection übergeben kann (dabei übergebe ich immer einen Zeiger von der Elternklasse Neuron)
    Wenn ich das ohne new erstelle und den Zeiger an den Connection Konstruktur ->

    class Connection {
    
    	Neuron *_neuron;
    	float _weight;
    
    public:
    	Connection(Neuron *neuron, float weight) {
    		_neuron = neuron;
    		_weight = weight;
    
    	}
            ...
    }
    

    übergebe,

    Connection con(it_i, arr[i++]);
    

    sagt mir der Compiler:

    no matching function for call to 'Connection::Connection(__gnu_cxx::__normal_iterator<InputNeuron*, std::vector<InputNeuron> >&, __gnu_cxx::__alloc_traits<std::allocator<float> >::value_type&)'
    

    wobei it_i der Iterator ist, der über den Vector inputNeurons läuft.

    Bin wohl schon etwas eingerostet 😳

    Danke für eure schnellen Antworten.

    PS: Die getOutputNeurons() Funktion ist nur zum testen geschrieben, soll also nicht bleiben.



  • @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    Ich erstelle die Neuronen mit new, um einen Zeiger zu erhalten, den ich an eine neue Connection übergeben kann (dabei übergebe ich immer einen Zeiger von der Elternklasse Neuron)

    Du speicherst aber nicht ein vector von Zeigern, sondern ein vector von Objekten. std::vector<InputNeuron> ist nicht dasselbe wie std::vector<InputNeuron*> oder std::vector<std::unique_ptr<InputNeuron>>


  • Mod

    @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    Okay, also es liegt wohl am Kopieren.
    ich verwende nun anstatt list einen vector, dabei bleibt das Problem aber bestehen.

    Natürlich bleibt das Problem, schließlich macht der vector auch Kopien. Alle Container machen Kopien. Wurde oben schon gesagt.

    Wichtiger ist aber: Wieso ist das ein Problem? Was sollen die Zeiger? Macht die Neuronenliste wilde Änderungen durch? Falls nein, dann benutz Vector und den Vectorindex als Referenz. Falls ja, benutz die List, aber merk dir halt den Zeiger auf das Listenelement, nicht den Zeiger auf das Element vor dem Einfügen.



  • @SeppJ sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    Wichtiger ist aber: Wieso ist das ein Problem? Was sollen die Zeiger? [..]
    Wie gesagt, ich hatte es so gelernt, dass wenn ich im Klassenkonstruktor einen Zeiger vom Elterntyp erwarte, ich alle abgeleiteten Klassen übergeben kann. Da ich mehrere Neuronentypen habe, die aber alle in eine Connection reinkönnen sollen, habe ich eben Die zeiger an die Connection übergeben.



  • @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    (Eine unschöne Lösung wäre wohl return &(inputNeurons.back()); in createNewInput)

    Und eine fehleranfällige noch dazu. Wenn std::vector Speicher für neue Elemente anfordern muss werden neue Kopien erzeugt und damit werden die alten Adressen ungültig.
    Verpacke dein Neuron in einen std::unique_ptr und gib den Rohzeiger zurück, damit biste auf jeden Fall auf der sicheren
    Seite:

    class NeuralNetwork
    {
       std::vector<std::unique_ptr<WorkingNeuron>> Neurons_;
    public:
       ...
       WorkingNeuron* create_neuron()
       {
          Neurons_.emplace_back( new WorkingNeuron() );
          return Neurons_.back().get();
       }
    };
    

    An deinem sonstigen Interface ändert sich nix, du hast eine saubere Lebenszeitverwaltung und gültige Adressen. Kannst halt leider nur keine Kopie des Vektors zurückgeben, aber das wolltest du ja sowieso nicht 😉



  • Danke für den konkreten Codeteil. Ein Problem habe ich allerdings noch beim Iterieren über diesen vector.

    Das ist mein Verdrahtung:

    for(auto it_o = OutputNeurons_.begin(); it_o != OutputNeurons_.end(); ++it_o) {
    	for(auto it_i = InputNeurons_.begin(); it_i != InputNeurons_.end(); ++it_i) {
    		Connection con(*it_i, arr[i++]);
    		(*it_o)->addConnection(con);
    		}
    	}
    

    Beim erstellen der Connection bekomme ich folgenden Fehler:

    error: no matching function for call to 'Connection::Connection(std::unique_ptr<InputNeuron>&, __gnu_cxx::__alloc_traits<std::allocator<float> >::value_type&)'
    

    Mein Connection Konstuktor:

    Connection(Neuron *neuron, float weight) {
    		_neuron = neuron;
    		_weight = weight;
    	}
    

    Meiner Meinung nach sollte *it_i doch auf die Adresse zum InputNeuron zugreifen können, oder nicht? Oder liegt das problem am unique_ptr?



  • @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    std::list<WorkingNeuron> getOutputNeurons() {

    OT:

    • hier erzeugst du eine Kopie der Liste
    • wenn man schon getter verwendet, sollten sie const sein.


  • @DocShoe sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    (Eine unschöne Lösung wäre wohl return &(inputNeurons.back()); in createNewInput)

    Und eine fehleranfällige noch dazu. Wenn std::vector Speicher für neue Elemente anfordern muss werden neue Kopien erzeugt und damit werden die alten Adressen ungültig.
    Verpacke dein Neuron in einen std::unique_ptr und gib den Rohzeiger zurück, damit biste auf jeden Fall auf der sicheren
    Seite:

    class NeuralNetwork
    {
       std::vector<std::unique_ptr<WorkingNeuron>> Neurons_;
    public:
       ...
       WorkingNeuron* create_neuron()
       {
          Neurons_.emplace_back( new WorkingNeuron() );
          return Neurons_.back().get();
       }
    };
    

    An deinem sonstigen Interface ändert sich nix, du hast eine saubere Lebenszeitverwaltung und gültige Adressen. Kannst halt leider nur keine Kopie des Vektors zurückgeben, aber das wolltest du ja sowieso nicht 😉

    Die Frage ist halt, was er mit dem Rückgabewert der Funktion machen will. Will er den Pointer irgendwo speichern oder einfach Funktionen des Objekts aufrufen?

    Im ersten Fall wäre es vielleicht besser, mit shared_ptr zu arbeiten, anstatt Roh-Pointer rauszugeben, mit denen man mitunter dumme Dinge machen kann ( delete ). Wenn er nur Funktionen der NeuronenKlasse aufrufen will, wäre vielleicht die Rückgabe einer Referenz besser.
    Ich persönlich bin kein Freund von Roh-Pointern, wenn das "Original" in einem Smartpointer vergraben ist und die Besitzrechte dort liegen.

    Da er aber ganz offensichtlich ein KNN baut ( egal welche Art ), arbeitet er mit Vernetzung, das heißt er will den zurückgegebenen Pointer vermutlich irgendwo anders speichern. In dem Fall wäre ich hier ein Freund vom shared_ptr oder der Rückgabe einer ID, mit der man in der NeuralNetworkklasse eine Referenz auf das originale Objekt ( im unique_ptr) abholen kann.


  • Mod

    @DocShoe sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    Die STL arbeitet mit Kopien, sobald du ein Objekt in std::list einfügst speichert die Liste eine Kopie des Objektes. Daher die unterschiedlichen Adressen.
    Mögliche Lösungen:

    • benutze std::unique_ptr und leg die in einem std::vector ab. Wenn es keinen wichtigen Grund für std::list geben sollte ist std::vector der Container der Wahl. Der rohe Zeiger des std::unique_ptr kann dann zur Verwaltung der Verbindungen benutzt werden

    Wieso sollte vector<unique_ptr<T>> besser als list<T> (oder evtl. forward_list<T>) sein, wenn es nur darum geht, stabile Zeiger auf T zu erhalten? Mir scheint, dass die explizite Indirektion vector<unique_ptr<T>> nur unnötig verkompliziert: Es sei denn, man möchte noch zusätzlich vom random-access profitieren.

    class NeuralNetwork
    {
       std::forward_list<WorkingNeuron> Neurons_;
    public:
       ...
       WorkingNeuron* create_neuron()
       {
          return &Neurons_.emplace_front();
       }
    };
    

    Wenn überhaupt käme ggf. vector<WorkingNeurons> infrage, nämlich dann, wenn nach der Erzeugung keine Neuronen eingefügt oder gelöscht werden sollen.



  • @camper
    Ja, eben. Wenn... dazu hat uns TE aber nix erzählt. Wenn der vektor ein Mal erzeugt und danach nie wieder angefasst wird braucht man auch keine smart_ptr, da gebe ich dir völlig recht.



  • Also Neuronen sollten durchaus noch hinzugefügt werden können, da will ich mich nicht festlegen ehrlich gesagt, zumindest jetzt.

    @DocShoe hast du noch einen Tipp wegen meines obrigen Problems?

    LG



  • @wernersbachr Ich bin zwar nicht DocShoe, aber mein wichtigster Tipp lautet: überleg dir genau, wer die Neuronen besitzt und wo du Verweise auf Neuronen brauchst. Und an welchen Stellen du ggf. die Neuronen kopieren möchtest. Überlege dir, ob ein vector (oder eine Liste, falls das hier besser sein sollte) die Neuronen oder Verweise auf die Neuronen enthalten soll. Ich denke, dass dieses für dich bzw. für deinen Code aktuell das größte Problem darstellt.



  • @It0101
    Ja, da hast du recht. Rohe Zeiger könnten dazu einladen sie zu löschen, wenn man sie nicht mehr braucht.
    Ich kenne die Anforderungen von TE nicht, aber man könnte ein Neuron nicht-kopierbar machen durch Löschen des Kopierkonstruktors und Zuweisungsoperators und stattdessen eine Referenz zurückgeben.

    @wernersbachr
    Du musst den iterator zwei Mal dereferenzieren, da der iterator auf einen std::unique_ptr zeigt.
    Aber denk bitte drüber nach, was wob dir geraten hat.



  • Ich weiß ehrlich gesagt nicht, auf welches Ergebnis ihr genau hinauswollt 🤓
    Würdet ihr die Zeiger oder die Objekte in einem vector verwalten?

    edit: Macht es Sinn, wie ursprünglich zu realisieren, nur anstatt eine Adresse eine Referenz zurückzugeben?



  • @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    Ich weiß ehrlich gesagt nicht, auf welches Ergebnis ihr genau hinauswollt 🤓
    Würdet ihr die Zeiger oder die Objekte in einem vector verwalten?

    Darum geht es eigentlich:

    @camper sagte in [Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte]

    wenn es nur darum geht, stabile Zeiger auf T zu erhalten?

    Ein vector mit Objekten ist halt nicht stabil bzgl. Zeiger.
    Also sowas in der Art

    A* a = &vec[99];
    

    ist problematisch, weil a ungültig werden kann (z.B. nach einem push_front). Bei einer List hast du das Problem nicht (hatte ich eben auch nicht mehr auf dem Schirm).

    Also entweder vector mit unique_ptr oder list mit Objekten.



  • @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    Ich weiß ehrlich gesagt nicht, auf welches Ergebnis ihr genau hinauswollt 🤓
    Würdet ihr die Zeiger oder die Objekte in einem vector verwalten?

    Das kommt ganz darauf an 😉

    Wenn du den Vektor ein Mal befüllst und danach nie wieder anfasst:
    Speichere die Objekte im Vektor und gib die Adresse der Objekte zurück.

    Wenn du nur Objekte hinzufügst und niemals entfernst:
    Speichere die Objekte im Vektor und gib den Index des Objekts im Vektor zurück.

    Wenn du den Vektor beliebig verändern willst:
    Speichere smart_ptr (unique_ptr oder shared_ptr) im Vektor und gib Referenzen (oder shared_ptr) der Objekte zurück.

    Zu deinem Edit:
    Nein. Ob Zeiger oder Referenz macht keinen Unterschied, bei einer Kopie wird beides ungültig.



  • @wernersbachr sagte in Objekt über Zeiger ist ein anders, als dasselbe in Liste gespeicherte:

    Ich weiß ehrlich gesagt nicht, auf welches Ergebnis ihr genau hinauswollt 🤓
    Würdet ihr die Zeiger oder die Objekte in einem vector verwalten?

    edit: Macht es Sinn, wie ursprünglich zu realisieren, nur anstatt eine Adresse eine Referenz zurückzugeben?

    Zunächst musst du dir über deine Anforderungen klar werden.

    Willst du Verweise auf NeuronenObjekte irgendwo anders noch speichern? Also braucht eine andere Klasse Zugriff auf das NeuronenArsenal?

    Wie bildest du die Synapsen/Connections ab?

    Wenn du das z.B. so machst:

    class Synapse
    {
    // ... bla bla konstruktoren
    private:
        std::shared_ptr<WorkingNeuron> SenderNeuron, ReceiverNeuron;
        double SynapseWeight = 0, NMDA = 0, AMPA = 0;
    }
    

    dann hast du eine andere Klasse, die in irgendeiner Form Verweise auf zwei Neuronen hat.
    D.h. du brauchst außerhalb der NeuralNetwork-Klasse eine Möglichkeit direkt auf Neuronen zu verweisen. Entweder indirekt über eine ID ( Index im Vector in NeuralNetwork ) oder direkt über Referenzen oder shared_ptr.

    Damit entfällt dann z.B. die std::unique_ptr-Lösung.
    Wenn die Menge an Neuronen konstant ist, dann geht das mit Referenzen am schönsten, ansonsten mit shared_ptr.

    Sobald du dir über deine Anforderungen klar geworden bist, kristallisiert sich in aller Regel eine "beste Lösung" raus.


Anmelden zum Antworten