Zeiger auf Liste von Zeigern .. oder so was



  • Hallo 🙂

    Diesmal ist es was kleines, aber ich krieg's irgendwie grad nicht hin ..

    Also, eine Funktion soll der eigenen Liste aus Objekten eine beretis erstellte übergeben. Diese ist aber (für meine Verhältnisse) sehr komisch definiert, und ich bekomme seit 40 min nur noch Gemecker vom Compiler, weil ich Referenzen und Zeiger nicht richtig stelle.

    Die Lsite ist definiert als

    std::list<Signal*>* mySignalList;
    

    Die Funktion ist definiert als

    bool Signals::setSignalList(std::list<Signal*>*& aSignalList){
        if(aSignalList != NULL){
            this->mySignalList = aSignalList;
            return true;
            }else{
                this->mySignalList = NULL;
                return false;
            }
        }
    

    Ich definiere meine eigene Liste als

    std::list<Signal*> SignalListTemp;
    

    An diesr Stelle kommt die Bemerkung "Ja deiniere doch SignalListTemp auch als list<Signal*>* SignalListTemp" - aber das will ich nicht, weil ich dann an sehr vielen Stellen noch schrauben müßte. Also will ich dann die so definierte Liste der obigen Funktion übergeben - als

    Signals->setSignalList(&SigFile->SignalListTemp);
    

    und schaff es nicht 😞 Der Compiler meint

    cannot convert parameter 1 from 'std::list<_Ty> *' to 'std::list<_Ty> *&'

    Weiß jemand wie die Zeile auszusehen hat?

    Viele Grüße,
    C.



  • Die Adresse ist nirgendwo gespeichert, also kannst Du darauf nicht referenzieren. Ist aber auch unnötig, wieso kopierst Du die Adresse nicht einfach, indem Du & weglässt? Du willst doch nicht den Zeiger verändern, sondern das, was dahinterliegt.



  • Du, die Zeiger sind nicht gerade meine Leidenschaft ..

    Ich hab das & weggelassen, aber das funktioniert nicht:

    cannot convert parameter 1 from 'std::list<_Ty>' to 'std::list<_Ty> *&'

    Eigentlich, bevor ich im Forum geschrieben habe, hab ich alle Kombinationen von (mehreren) & und * ausprobiert, die mir eingefallen sind 😃 Ich glaube ich sollte die Zeiger-Sache (wieder) einmal durchlesen. Ich verspreche es auch bei Gelegenheit zu machen, aber grad brauch ich das (mehr oder weniger) dringend ..

    Danke für die schnelle Antwort übrigens 🙂



  • Wenn

    bool Signals::setSignalList(std::list<Signal*>*& aSignalList);
    

    vorgegeben ist, dann erzwingt die Funktion, dass aSignalList im Freispeicher liegen muss:

    typedef std::list<Signal*> siglist;
    siglist *SignalListTemp = new siglist;
    Signals->setSignalList(SignalListTemp);
    

    Aber wenn Signal::mySignalList wirklich eine Referenz auf siglist* wäre, dann könnte der neue Wert nur in einer Initialisierungsliste gesetzt werden. Es wird also wahrscheinlich nur der Speicherbereich gesichert, die Übergabe per Referenz ist wie geschrieben nur eine Sicherheitsmaßnahme, dass man setSignalList nicht mit einer Stack-Variablen aufruft, die evtl. vorzeitig zerstört wird.

    Aber um das wirklich endgültig zu sagen, fehlt noch weiterer Code.



  • Du sollst nicht das & bei der Übergabe, sondern das in der Parameterliste weglassen (so dass es std::list<Signal*>* aSignalList heißt).
    Anschließend beschäftigst du dich mit Call-by-Value und Call-by-Reference und versuchst zu verstehen, warum es vorher nicht ging. Mit den Zeigern direkt hat es übrigens wenig zu tun.

    @_Falke: das ist falsch, der Zeiger kann auch auf den Stack zeigen, aber es muss eben ein Zeiger als LValue da sein. Folgendes würde also auch funktionieren:

    std::list<Signal*>* ptr = &SigFile->SignalListTemp;
    Signals->setSignalList(ptr);
    

    Denn dann wird ein LValue und kein RValue übergeben.



  • Ich hatte es so verstanden, dass

    bool Signals::setSignalList(std::list<Signal*>*& aSignalList);
    

    nicht geändert werden kann.
    Mit

    std::list<Signal*> SignalListTemp;
    std::list<Signal*>* ptr = &SignalListTemp;
    Signals->setSignalList(ptr);
    

    könnte man zwar den Mechanismus aushebeln, ich glaube aber nicht, dass dies vom Entwickler von Signals so vorgesehen ist.



  • Die Funktion benötigt die Referenz gar nicht, da hätte ein Zeiger ausgereicht. Daher ist dein Workaround vollkommen in Ordnung. Was der Bastler vorgesehen hat, weiß ich nicht. Vielleicht einfach so ein Fehler wie bei boost::ptr_map::insert...



  • Bei boost::ptr_map ist es keinesfalls ein Fehler, sondern wichtig für die Exceptionsicherheit. Da hier aber Pointer übergeben werden, braucht man die Referenz nicht, wenn man den übergebenen Pointer nicht verändern will.



  • Da das hier ja jetzt geklärt ist: Wieso sorgt das für Exceptionsicherheit und ist die STL dann nicht exceptionsicher?



  • Noch was zu *&:
    Zeiger und Referenzen haben zwei Existenzgründe:
    ➡ Beim Übergeben von Argumenten muss nicht das ganze Objekt übergeben werden (Geschwindigkeit)
    ➡ Man kann das Objekt verändern.

    Punkt 1 wird ja schon durch den Zeiger () gewährleistet (& hat die gleiche Größe wie 😉 und Punkt 2 ebenfalls. Also ist die Referenz sowieso sinnlos, außer du willst den Zeiger verändern (wie Eisflamme oben geschrieben hat).
    Hoffe das war so korrekt.
    mfg paulrei



  • Vielen Dank, hab ich korrigiert.

    Ich probier immer noch rum, um etwas Routine zu entwickeln, und ich hätte noch eine Frage: Wie kann ich an

    std::list<Signal*>* SignalListTemp;

    ein *Signal hinzufügen? Mit push_back auf jeden Fall nicht.

    Und wo wir schon dabei sind: Wenn ich (aus Versehen) SignalListTemp mit this->SignalListTemp.clear() in dem Destruktor "reinigen" will, wird das sogar kompiliert - obwohl es richtig

    this->SignalListTemp = NULL;
    

    heißen soll. Warum verursacht das nicht schon beim Kompilieren n Fehler?



  • Dir fehlt einiges an Grundlagenwissen, fürchte ich.

    Signal* signal = new Signal(/*...*/);
    SignalListTemp->push_back(signal);
    

    funktioniert.

    Und sowohl deine clear()- als auch deine = NULL-Variante sind genau so sinnfrei wie syntaktisch korrekt, warum sollte es da einen Compilerfehler geben?



  • Eisflamme schrieb:

    Da das hier ja jetzt geklärt ist: Wieso sorgt das für Exceptionsicherheit und ist die STL dann nicht exceptionsicher?

    Die Exception-Garantien der Boost.PointerContainers sind stärker als die der STL. Da normalerweise nichts kopiert werden muss, können weniger Fehler auftreten, und mehr Rollbacks sind möglich.

    std::list<Signal*>*&
    

    😮 Ich kann mir schwer vorstellen, dass sowas sinnvoll ist. Schon std::list<Signal*>* ist grenzwertig, erst recht wenn man new und delete benutzt.

    Und Cordula: Gibt es einen zwingenden Grund, hier manuelle Speicherverwaltung zu verwenden?



  • Eisflamme schrieb:

    Da das hier ja jetzt geklärt ist: Wieso sorgt das für Exceptionsicherheit und ist die STL dann nicht exceptionsicher?

    Das wird sogar in der FAQ erklärt. Nehmen wir an, es ginge (Parameter wäre keine Referenz, sondern Call-by-Value):

    ptr_map<MyClass, int> map;
    map.insert(MyClass(), new int(0));
    

    Die Reihenfolge der Parameterauswertung ist nicht festgelegt, also kann folgendes passieren:

    1. evauliere new int(0) - OK
    2. evaluiere MyClass() - !
    3. evaluiere MyClass-Kopierkonstruktor - !
    

    Die Stellen mit einem Ausrufezeichen könnten eine Exception werfen, was zu einem Memoryleak führen würde (da der Pointer noch nicht im Besitz der Map ist). Der Kopierkonstruktor wird verhindert, indem eine Referenz verlangt wird und der Konstruktoraufruf wird verhindert, indem diese Referenz non-const sein muss. Nur so kann gewährleistet werden, dass bei der Übergabe des Keys keine Exception geworfen werden kann.

    @Cordula: wenn SignalListTemp ein Pointer ist, dann geht SignalListTemp = NULL , aber das löscht nicht die Liste. Wenn es kein Pointer ist, kannst du der Liste nicht NULL zuweisen. Beschäftige dich bitte nochmal mit Zeigern, sowas kann ein Buch viel besser erklären als ein Forum.
    Das push_back ginge übrigens mit SignalListTemp->push_back(...) , was du aber auch schon wissen solltest, hättest du Zeiger verstanden.



  • @Nexus: Nicht meine Idee, mit der manuellen Speicherverwaltung (die mich übrigens schon 2 Tage gekostet hat).

    @ipsec: Ja, ich weiß, ich les es noch mal wenn ich etwas mehr Zeit habe (also wenn ich das hier fertig habe .. )

    Wg push_back() : Klar, ich hab's mit -> drin, wird auch kompiliert. Das Programm stürzt allerdings ab wenn es zu der Zeile kommt.

    Und, ja, ich weiß dass ich mit = NULL die Lsite nicht lösche. Ich will auch dass der Zeiger auf einen bekannten Wert zeigt (eben NULL), um nachher prüfen zu können, ob drin schon was steht/ob die verändert worden ist (== NULL). Macht das Sinn?



  • Cordula schrieb:

    @Nexus: Nicht meine Idee, mit der manuellen Speicherverwaltung (die mich übrigens schon 2 Tage gekostet hat).

    Merk dir einfach, dass C++ so nicht aussieht. So ein Gefrickel ist nur sehr selten nötig. Für die anderen Fälle gibt es RAII.

    Cordula schrieb:

    Und, ja, ich weiß dass ich mit = NULL die Lsite nicht lösche. Ich will auch dass der Zeiger auf einen bekannten Wert zeigt (eben NULL), um nachher prüfen zu können, ob drin schon was steht/ob die verändert worden ist (== NULL). Macht das Sinn?

    Du solltest zwischen einem nicht existenten und einem leeren Container-Objekt unterscheiden. Entsprechend prüfst du auf NULL oder empty() .



  • Wg push_back() : Klar, ich hab's mit -> drin, wird auch kompiliert. Das Programm stürzt allerdings ab wenn es zu der Zeile kommt.

    Du hast den vector doch bestimmt nicht mit new initialisiert, oder? Das haste jetzt von deinem wahrscheinlich unnötigen Zeiger.

    Und, ja, ich weiß dass ich mit = NULL die Lsite nicht lösche. Ich will auch dass der Zeiger auf einen bekannten Wert zeigt (eben NULL), um nachher prüfen zu können, ob drin schon was steht/ob die verändert worden ist (== NULL). Macht das Sinn?

    Das habe ich nie implizieren wollen. Du bist im destructor. Nachher wird niemand mehr diese Variable anrühren, weil das Objekt weg ist. Wozu also nullen?



  • Cordula, die beste Strategie ist wahrscheinlich, Dich näher mit den C++ Grundlagen auseinander zusetzen. Dir scheint ein wichtiges Grundverständnis bzgl verschiedener Speicherbereiche, Objekt-Lebenszeiten, Objekt-Besitz, Zeiger und Referenzen zu fehlen. Du gibst sogar zu, hier im Dunkeln zu tappen und mehrere Dinge einfach "auszuprobieren" ohne komplett verstanden zu haben, was Du eigentlich machst. Das ist besonders bei C++ eine schlechte Idee. Andere Sprachen mögen Dir gewisse Dinge verzeihen (zB Java mit Referenzen, die praktisch nie ungültig werden können und wo alles Mögliche zur Laufzeit überprüft wird wie zB ob ein Zeiger null ist, ob ein Array-Index auch ja nicht zu groß ist, etc etc etc). In C++ läufst Du ratzfatz in die "UB-Falle", wenn Du nicht weißt, was Du tust.

    Cordula schrieb:

    Die Lsite ist definiert als

    std::list<Signal*>* mySignalList;
    

    mySignalList ist keine Liste. mySignalList ist ein Zegier und kann auf ein Objekt des Typs list<Signal*> zeigen.

    Cordula schrieb:

    Die Funktion ist definiert als

    bool Signals::setSignalList(std::list<Signal*>*& aSignalList){
        if(aSignalList != NULL){
            this->mySignalList = aSignalList;
            return true;
            }else{
                this->mySignalList = NULL;
                return false;
            }
        }
    }
    

    Das & in list<Signal*>*& ist da total überflüssig. Solltest Du eher weglassen, da Dich ja gar nicht interessiert, wo der Zeiger des Aufrufers gespeichert ist und Du diesen (des Aufrufers) auch gar nicht ändern willst.

    Cordula schrieb:

    Ich definiere meine eigene Liste als

    std::list<Signal*> SignalListTemp;
    

    An diesr Stelle kommt die Bemerkung "Ja deiniere doch SignalListTemp auch als list<Signal*>* SignalListTemp"

    Woher? Von einem Kollegen? Hat er einen Grund dafür? Ich könnte mir da schon ein paar Gründe vorstellen. Das hängt aber von dem restlichen Design ab und kann so (ohne das Wissen über das restliche Design) gar nicht vernünftig diskitiert werden, was "rightig".

    Cordula schrieb:

    aber das will ich nicht, weil ich dann an sehr vielen Stellen noch schrauben müßte.

    Das ist kein Grund. Ein Grund für die Verwendung von list<Signal*> ist, dass das Objekt in Deinem Fall lang genug leben würde. Gegen list<Signal*> spricht, dass es in Deinem Fall nicht lang genug leben könnte. Ob das eine oder das andere zutrifft, können wir nicht feststellen, da du zuwenig verraten hast.

    Cordula schrieb:

    Also will ich dann die so definierte Liste der obigen Funktion übergeben - als

    Signals->setSignalList(&SigFile->SignalListTemp);
    

    und schaff es nicht 😞 Der Compiler meint

    cannot convert parameter 1 from 'std::list<_Ty> *' to 'std::list<_Ty> *&'

    Weiß jemand wie die Zeile auszusehen hat?

    Ja, Du versuchst eine nicht-konstante Lvalue-Referenz mit einem Rvalue-Ausdruck zu initialisieren. Das ist genauso sinnfrei wie das hier:

    void foo(int&);
    void blah() {
      foo(1729); // geht nich
      int i = 2;
      foo(i);    // ok
    }
    

    1729 ist einfach nur ein Wert. Du kannst eine nicht-konstante Referenz nicht mit einem Wert initialisieren. Du initialisierst sie mit etwas, was sich auf ein Objekt bezieht. Ein Objekt ist etwas, was irgendwo im Speicher sitzt und Werte speichern kann. In Deinem Fall ist der Wert eine Adresse, die Du über den &-Operator bekommst.

    Tue Dir einen Gefallen und leg Dir ein gescheites C++ Buch zu. Wahrscheinlich unterschätzt Du im Moment auch Deine Wissenslücken.

    kk


Anmelden zum Antworten