Frage zu Parameter: const-Referenz oder Pointer?



  • Hallo,

    ich möchte einem Konstruktor eine stl-map als Parameter übergeben, und die Map dann einer Membervariable zuweisen, um innerhalb der Klasse nicht immer Funktionsparameter übergeben zu müssen. Eine Kopie der Map möchte ich aber vermeiden. Lässt sich das auch irgendwie mit einer const-Referenz machen, oder muss ich dazu einen Pointer verwenden?

    Bis jetzt schaut es so aus:

    class MyClass
    {
      public:
        MyClass(const std::map<string,string>* pMap);
      private:
        std::map<string,string>::const_iterator m_it;
        std::map<string,string>* m_map;
    };
    
    MyClass::MyClass(const std::map<string,string>* pMap)
    {
      m_map = pMap;
    }
    

    Ich find's halt unschön dass ich dann andauern dereferenzieren muss etc, und außerdem soll man ja (soweit ich weiß) möglichst auf Referenzen anstatt Pointer setzen.


  • Administrator

    Als Parameter kein Problem:

    class MyClass
    {
    private:
      std::map<std::string, std::string> const* m_map;
    
    public:
      MyClass(std::map<std::string, std::string> const& map)
        : m_map(&map) // Adresse holen und im Zeiger speichern.
      {
      }
    };
    

    Den folgenden Satz verstehe ich allerdings nicht:

    Radix schrieb:

    Ich find's halt unschön dass ich dann andauern dereferenzieren muss etc, ...

    Dereferenzieren? Inwiefern? Du kannst auch einfach den operator -> bei einem Zeiger verwenden:

    bool MyClass::hasEntry(std::string const& str)
    {
      return m_map->find(str) != m_map->end();
    }
    

    Grüssli



  • Dereferenzieren? Inwiefern? Du kannst auch einfach den operator -> bei einem Zeiger verwenden:

    der dereferenziert doch auch 😕



  • Hallo,

    erstmal danke für die fixe Antwort.

    Ich hab das mit dem Dereferenzieren so gemeint dass ich zu vermeiden versuche, irgendwann mit *((*a)->b) ... zu arbeiten, sondern lieber auf "normale" Variablen zugreife, wie es z.B. mit Referenzen der Fall ist. Aber in diesem Fall scheint dass nicht so einfach zu gehen, ist aber auch kein Weltuntergang.

    Wie verhält sich das eigentlich mit dem const - wo genau liegt hier der Unterschied?

    MyClass(std::map<std::string, std::string> const& map)
    
    MyClass(const std::map<std::string, std::string>& map)
    

    Wenn es sich im obigen Bsp auf den Inhalt der Map bezieht, was macht das const dann im unteren Beispiel?



  • Hi,

    Edit: Zu langsam für Dravere... 😉
    also Referenzen und Zeiger haben bei allen Gemeinsamkeiten diverse Unterschiede.
    Fragen, die Dir bei der Entscheidung für die richtige "Verweisform" helfen können:
    - ist der Verweis "optional"? Referenzen müssen immer auf ein und dasselbe Objekt verweisen - soll der Verweis mal "ins Leere" ( 0 ) oder mal auf ein anderes Objekt verweisen, kommen nur Zeiger infrage.
    - Besonders, mit einem Verweis als Membervariable: Ist sichergestellt, dass das referenzierte Objekt (also die "außen erzeugte" std::map) mindestens dieselbe Lebensdauer hat wie das Objekt Deiner Klasse? Sowas geht nämlich schief (und wird üblicherweise von keinem Compiler erkannt):

    class MyClass
    {
    private:
      map<string, string> const& m_m;
    public:
      MyClass(map<string, string> const& m) : m_map(map) {}
      string f() { return m["Hallo"]; }
    }; 
    
    map<string, string> makeMap() {
       map<string, string> m;
       m["Hallo"] = "HuHu";
       return m;
    }
    
    // Offensichtlich
    int main() {
       map<string, string>* m = new std::map<string, string>;
       MyClass a(*m);
       delete m;
       a.f(); // Booom
    
    // weniger offensichtlich
       MyClass b(makeMap());
       b.f(); // Booom
    

    Übrigens: Es gibt genauso const-Zeiger, wie es const-Referenzen gibt.

    Ein großer Vorteil von Referenzen ist aber IMHO gerade diese "Existenzsicherheit" (ja ich kenne den alten "dynamisch und zerstören-Trick" s.o.): Du bist nicht dafür verantwortlich, dass das Objekt erzeugt/zerstört/gültig/... ist. Das sind alles Fragen, die "ins Design" verschoben sind: Der Aufrufer kann Dir viel schlechter aus Versehen ein ungültiges Objekt geben und er weiß schon anhand der Schnittstelle, dass Du ein gültiges erwartest.

    Das Einsparen von ein wenig ->/*-Tipparbeit halte ich für kein wirklich ausschlaggebendes Kriterium.

    Gruß,

    Simon2.



  • Okay, ich beschreibe doch mal den Zweck dieser Geschichte, vielleicht bin ich mit meiner Lösung ja auf dem falschen Weg:

    Ich habe eine Mehrfachvererbung von Klassen (sog. Kanäle). Es gibt außerdem eine Klasse die eine Datei zeilenweise einliest. Eine solche Zeile ist grob nach dem Muster

    Kanaltyp(Key1=Value1, Key2=value2, ...)
    

    aufgebaut und Key-Value-Paare werden in die Map eingelesen. Die "Einlese-Klasse" sieht am ersten "Token" um welchen Kanal es sich handelt.

    string s1 = ... // 1. token einlesen
    map<string,string> mmap = ...; // restl. Zeile einlesen
    
    if(s1 == kanaltyp1)
      createKanaltyp1();
    else if(s1 == kanaltyp2)
      createKanaltyp1();
    ...
    
    createKanaltyp1()
    {
      kanal* = new Kanaltyp1(&mmap);
    }
    
    Kanaltyp1::Kanaltyp1(map<string,string> const* mmap):Basiskanal(mmap)
    {
      // map auswerten und membervar setzen
    }
    
    Basiskanal::Basiskanal(map<string,string> const* mmap)
    {
      // Basiskanal hat Membervariable "map<string,string> const* m_map"
      m_map = mmap;
    };
    

    Da die Kanäle viele Gemeinsamkeiten haben, können ein paar der Key-Value-Paare in den Basisklassen ausgewertet werden, andere wiederum nicht.

    Wahrscheinlich ist's nicht richtig hierfür eine Member variable zu benutzen, weil ich den Inhalt der Zeile nach dem Konstruktor nicht mehr brauche. Auf der anderen Seite geht auch kein Speicher verloren (außer fürs Speichern der Adresse), weil die ganzen Zeilen-Maps in der Einlese-Klasse ja wieder gelöscht werden.


  • Administrator

    JustAnotherNoob schrieb:

    Dereferenzieren? Inwiefern? Du kannst auch einfach den operator -> bei einem Zeiger verwenden:

    der dereferenziert doch auch 😕

    Ja, allerdings implizit und er heisst nicht Dereferenzierungsoperator. Wenn man von Dereferenzierung redet, dann denkt man meistens nicht an den Class-Member-Access-Operator. Vor allem auch Anfängern ist meistens nicht bewusst, dass hier eine implizite Dereferenzierung stattfindet.

    Radix schrieb:

    Wie verhält sich das eigentlich mit dem const - wo genau liegt hier der Unterschied?

    MyClass(std::map<std::string, std::string> const& map)
    
    MyClass(const std::map<std::string, std::string>& map)
    

    Hat die gleiche Bedeutung. Es ist einfach eine andere gültige Schreibweise.

    Radix schrieb:

    Wahrscheinlich ist's nicht richtig hierfür eine Member variable zu benutzen, weil ich den Inhalt der Zeile nach dem Konstruktor nicht mehr brauche.

    Wie bitte? Das ist eine Map, welche nur zur Konstruktion des Objektes benötigt wird und danach nie wieder im Objekt? Dann ist es definitiv nicht korrekt, den Zeiger als Member zu speichern und somit nur deiner Schreibfaulheit zu dienen. Notfalls kann man sich schliesslich ein typedef machen, damit man nicht immer komplett std::map<std::string, std::string> const& hinschreiben muss.
    Ich frage mich allerdings sogar, ob die Interpretation der Map in die Klasse gehört und nicht eher ausserhalb in eine Factory Methode.

    Grüssli



  • HI,

    t'schuldigung für die späte Antwort...

    Wie bitte? Das ist eine Map, welche nur zur Konstruktion des Objektes benötigt wird und danach nie wieder im Objekt? Dann ist es definitiv nicht korrekt, den Zeiger als Member zu speichern und somit nur deiner Schreibfaulheit zu dienen.

    Hm, im Moment speichere ich in jedem Kanal-Objekt tatsächlich einen Zeiger auf "seine" Map, die nach der Konstruktion dann aber in der Klasse, die den Konstr. aufruft, wieder gelöscht bzw. überschrieben wird.
    Die Alternative ist es dann, den Map-Zeiger innerhalb des Kanal-Objekts jeder Member-Funktion, die ein <string,string>-Paar aus der Map auswerten soll, als Parameter mitzugeben?

    Kann ich natürlich machen. Der Grund warum ich's bisher nicht so habe ist, dass ich's umständlich finde, dass ich innerhalb eines Objekts lauter Parameter an die Methoden übergeben muss. Ich weiß dass es irgendwo falsch ist, hab's aber öfter schon so gemacht. Genauso wie ich einen const_iterator als Membervariable deklariert habe, um ihn nicht in jeder Methode neu deklarieren zu müssen (was doch auch Performance-Vorteile haben könnte) oder als Parameter einen Pointer auf den Iterator übergeben zu müssen.

    Dravere schrieb:

    Ich frage mich allerdings sogar, ob die Interpretation der Map in die Klasse gehört und nicht eher ausserhalb in eine Factory Methode.

    Ich sehe in meinem Programm keine Nachteile wenn ich es in den Kanal-Klassen mache, kenn aber die Factory-Methode nicht und daher fehlt mir der Vergleich. Das schau ich mir vielleicht ein ander Mal an.

    Danke allen für die Tipps, Stephan



  • Ich kann Simon2 nur zustimmen. Man sollte sich sinvolle Regeln (Coding Standard) zurechtlegen.

    Ich verwende T const& meist als Optimierung für "logisches pass-by-value", wenn ich keine Kopie brauche und nur lesend auf das Objekt zugreifen möchte. Das ist eigentlich nur für Klassen-Typen interessant.

    Referenzen auf nicht-const, also T& , setze ich eher selten ein, halte sie aber zB bei swap(T&,T&) für sehr sinvoll.

    Simon2s Beispiel finde ich gut. Im Konstruktor wird die Adresse eines übergebenen Objektes einfach als Element gespeichert ("reference escaping", die Referenz entflieht dem "zeitlichen Gültigkeitsbereich" der Funktion). Um Probleme zu vermeiden und Programmcode leserlich zu halten, könnte man also folgende Benimmregeln aufstellen:

    1. Funktionen, die Referenzen entgegen nehmen, versprechen, diese nicht "entfliehen" zu lassen.
    2. Funktionen, die Zeiger entgegen nehmen, werden diese eventuell irgendwohin kopieren oder in anderer Form ablegen. Man muss sich in diesem Fall also als Aufrufer über die Lebenszeit der Objekte Gedanken gemacht haben.

    Gruß,
    SP


Anmelden zum Antworten