getName() für Attribut vom Typ Zeiger auf eigene Klasse



  • Hallo Community!

    Ich programmiere meistens in c und habe deshalb noch nicht so viel Ahnung von c++.
    Momentan hänge ich an folgendem Problem:
    Klasse B hat als Attribute zwei Zeiger auf Klasse A (ich weiß nicht, wie ich das hier mit Referenzen machen kann). Klasse A hat einen std::string mit getter-Methode.

    Ich wollte der Klasse B jetzt Getter für die Namen der Klasse-A-Objekte geben. Der Quellcode sieht so aus (Meldungen des Compilers (Visual Studio 2012) in den Kommentaren):

    #include <map>
    #include <string>
    using namespace std;
    
    class KlasseA{
        string name;
    public:
        KlasseA(string& n) : name(n) {}
        const string& sagName() const {
            return name;
        }
    };
    
    class KlasseB{
        KlasseA* elem1;
        KlasseA* elem2;
    public:
        KlasseB(map<string, KlasseA&> elems, const string& name1, const string& name2) : elem1(NULL), elem2(NULL) {
            map<string, KlasseA&>::iterator found = elems.find(name1);
            if(found != elems.end())
                elem1 = &(found->second);
            found = elems.find(name2);
            if(found != elems.end())
                elem2 = &(found->second);
        }
    
        const string& sagNameElem1() const {
            if(elem1)
                return elem1->sagName();
            return "no such elem"; // warning: Adresse einer lokalen Variablen oder eines temporären Werts wird zurückgegeben
        }
        const string& sagNameElem2() const {
            if(elem2)
                return elem2->sagName();
            return NULL; // warning: Adresse einer lokalen Variablen oder eines temporären Werts wird zurückgegeben
        }
        const KlasseA& sagElem1() const {
            if(elem1)
                return *elem1;
            return NULL; // error: 'return': 'int' kann nicht in 'const KlasseA &' konvertiert werden
        }
        const KlasseA& sagElem2() const {
            if(elem2)
                return *elem2;
            return *((KlasseA*)NULL); // Das ist doch mist...
        }
    };
    

    Die Variante in Zeile 27 wäre mir eigentlich am liebsten, aber dass das nicht geht ist mir auch klar.

    Müsste ich im Konstruktor nicht die Elemente aus der map rausfischen könnte ich Referenzen statt Zeiger nehmen und hätte das Problem nicht (oder doch?).

    Warum bekomme ich bei den Zeilen 35 und 40 unterschiedliche Meldungen? string hat doch auch keinen Konstruktor für int...

    Wie kann man das besser/richtig lösen? Oder sollte ich das Ganze Design nochmal überdenken?

    Edit: die const KlasseA& sagElemXY()-Methoden habe ich dann als Umweg zu den Namen gedacht...

    Viele Grüße aus Hamburg und vielen Dank für eure Hilfe!
    Matze



  • Hallo,

    bzgl. des Designs gibt es sicherlich noch Verbesserungsmöglichkeiten.

    Aber fangen wir mal mit dem konkreten Compilerproblem an:
    Wenn du eine Referenz bei einer Funktion zurückgeben willst, dann muß in allen return-Anweisungen auch eine Referenz auf den zurückzugebenden Datentypen vorhanden sein.
    Hier ist das Problem, daß die Klasse std::string und String-Literale (so wie du sie ja auch schon aus C kennst), nicht (ohne Konvertierung) kompatibel sind.

    Es gibt jetzt zwei Möglichkeiten:

    string sagNameElem1() const
    {
        if(elem1)
            return elem1->sagName();
        return "no such elem"; // <- hier ruft der Compiler dann explizit den std::string(const char *)-Konstruktor auf!
    }
    
    const string& sagNameElem1() const
    {
        static const std::string NoElem("no such elem");
    
        if(elem1)
            return elem1->sagName();
        return NoElem;
    }
    

    Alle weiteren Lösungen (bei Referenzrückgabe) mit NULL (0) sind komplett unsinnig, denn die Definition einer Referenz ist ja gerade, daß immer auf ein gültiges Objekt verwiesen wird.

    Kürzer würde man meine obigen Lösungen übrigens so schreiben:

    const string& sagNameElem1() const
    {
        static const std::string NoElem("no such elem");
    
        return elem1 ? elem1->sagName() : NoElem;
    }
    

    Aber wie oben schon angedeutet, finde ich das Design dieser Klassen nicht optimal. Du solltest dir unbedingt noch mal die sinnvolle Übergabe mittels "const" sowie "&" (Referenz) in einem C++ Buch anschauen.

    Als m.E. sinnvolle Verbesserungen deines Codes:

    // Übergabe als const-reference
    KlasseA(const string& n) : name(n) {}
    

    (damit man z.B. diese Klasse auch mit einem String-Literal ("test") aufrufen kann)

    // auch Übergabe als const-reference
    KlasseB(const map<string, KlasseA&> & elems, ...)
    

    (zumindestens für vor C++ 11)

    Du solltest jedoch designtechnisch überlegen, ob deine Klassen so viel Sinn machen, wenn sich die Map ändert (oder sollen extra diese Werte einmalig aus der Map geholt werden)?
    Ein weiteres Stichwort könnte hier "lazy evaluation" sein, d.h. du holst die Werte aus der Map erst bei konkretem Zugriff (und dann evtl. zusätzlich noch mit einem Cache für weitere Zugriffe).



  • Hallo Th69,

    bitte entschuldige die späte Rückmeldung, ich war die letzten zwei Tage offline.

    Vielen Dank für deine Antwort, die hat mir sehr geholfen. Z. B. lasse ich die Daten jetzt tatsächlich erstmal in der map und dass eine Referenz nicht NULL sein kann ist gut zu wissen (dachte, ich hätte das mal irgendwo gesehen 😮 ).

    Der Aufruf des string-Konstruktors in Zeile 30 war mir klar. Allerdings verstehe ich immer noch nicht, warum ich in Zeile 40 einen Error bekomme, in Zeile 35 aber "nur" eine Warnung.

    Außerdem stehe ich jetzt wieder an einer anderen Stelle auf dem Schlauch. Ich bin mir ziemlich sicher, dass es heute Nachmittag noch funktioniert hat 🙄

    Es geht um folgenden Code-Schnipsel:

    #include <iostream>
    #include <string>
    using namespace std;
    
    int main(){
    
        string line = "Max Mustermann\t -   \tJane Doe  \t  \t";
    
        string nameA = line.substr(0, line.find_first_of('-') - 1);
        nameA = nameA.substr(nameA.find_first_not_of(" \n\t\f\v\r"), nameA.find_last_not_of(" \n\t\f\v\r") + 1);
    
        string nameB = line.substr(line.find_first_of('-') + 1);
        nameB = nameB.substr(nameB.find_first_not_of(" \n\t\f\v\r"), nameB.find_last_not_of(" \n\t\f\v\r") + 1);
    
        cout << "X" << nameA << "X   X" << nameB << "X" << endl;
    
        return 0;
    }
    

    Ich möchte am Ende in nameA "Max Mustermann" und in nameB "Jane Doe" stehen haben. Bei Max klappt das noch, aber bei Jane habe ich noch ein

    "  \t "
    

    dahinter. Die Ausgabe ist also:

    XMax MustermannX   XJane Doe     X
    statt
    XMax MustermannX   XJane DoeX
    

    Frage 1: Was mache ich falsch?
    Frage 2: Hätte ich für die Frage einen neuen Beitrag aufmachen sollen?

    Vielen Dank für eure Hilfe und viele Grüße!
    Matze

    PS: dieses mal antworte ich auch etwas schneller 🙂 😉



  • Habe jetzt gemerkt, dass es mit

    string nameB = line.substr(line.find_first_of('-') + 1);
    nameB = nameB.substr(nameB.find_first_not_of(" \n\t\f\v\r"), nameB.find_last_not_of(" \n\t\f\v\r") + 1);
    nameB = nameB.substr(0, nameB.find_last_not_of(" \n\t\f\v\r") + 1);
    

    funktioniert. Dann ist es auch egal, wie viele '\t' am Ende standen. Aber verstehen kann ich das immer noch nicht. 😕
    Ich wäre für eine Erklärung sehr dankbar. Vielleicht ist es für mich einfach schon zu spät heute... 😞



  • Ok, ich habe es gefunden.
    Mann bin ich blöd 😡

    substr() erwartet keinen Angaben nach dem Motto "von, bis", sonder nach dem Motto "von, wieviele". Hatte an das Äquivalen in Java gedacht 🙄

    Viele Grüße
    Matze



  • Hallo,

    bzgl.

    Der Aufruf des string-Konstruktors in Zeile 30 war mir klar. Allerdings verstehe ich immer noch nicht, warum ich in Zeile 40 einen Error bekomme, in Zeile 35 aber "nur" eine Warnung.

    Das liegt darin, daß bei std::string implizit der std::string(const char *)-Konstruktor aufgerufen wird (d.h. es wird ein lokales temporäres string-Objekt erstellt). Hierbei ist aber zu beachten, daß gerade dieser Fall (nämlich NULL zu übergeben) undefiniertes Verhalten ist (also laut Spezifikation nicht erlaubt ist, s.a. string::string (unter "Exception safety")).

    Und bei der Klasse A gibt es eben keine implizite Konvertierung mittels eines Nullzeigers (bzw. der Zahl 0) und daher direkt einen Fehler. Kannst ja mal testweise bei KlasseA den Konstruktor KlasseA(int n) oder KlasseA(const void 😉 o.ä. hinzufügen (neu kompilieren und dann staunen ;-).



  • Hallo,

    Th69 schrieb:

    Das liegt darin, daß bei std::string implizit der std::string(const char *)-Konstruktor aufgerufen wird (d.h. es wird ein lokales temporäres string-Objekt erstellt). Hierbei ist aber zu beachten, daß gerade dieser Fall (nämlich NULL zu übergeben) undefiniertes Verhalten ist (also laut Spezifikation nicht erlaubt ist, s.a. string::string (unter "Exception safety")).

    Ah ja, na klar! Ich hatte mir nur die ctor-Prototypen angeschaut und gar nicht bis "Exception safety" gelesen. Dass bei NULL der char*-Konstruktor aufegrufen werden könnte hatte ich gar nicht in Betracht gezogen 🙄

    Danke und Gruß!
    Matze


Anmelden zum Antworten