std::string * in std::string **



  • Ich habe mir eine Klasse geschrieben, die als Member einen std::string hat. Dieser std::string soll allerdings auch in der Lage sein auf einen anderen std::string zu zeigen, der sich wiederum auch in einer Klasse befindet. Aus diesem Grund brauche ich auch den Doppelpointer. Ohne ihn könnte ich den Wert des std::string nicht ändern.

    enum ObjectType
    {
    	VAR,
    	REF
    };
    
    class Object
    {
    private:
    	ObjectType type;
    	std::string **val;
    
    public:
    	Object(std::string **v, ObjectType t);
    	~Object();
    
    	void setVal(std::string *v)
    	{
    		delete *val;
    
    		*val = v;
    	}
    
    	std::string **getVal()
    	{
    		return val;
    	}
    };
    

    Es muss also folgendes möglich sein:

    Object bla(createNewString("Wert"), VAR);
    std::cout << bla.getVal() << std::endl;   // Ausgabe: Wert
    
    Object blub(gla.getVal(), REF);
    blub.setVal("Bla"); 
    
    std::cout << blub.getVal() << std::endl; // Ausgabe: Bla
    std::cout << bla.getVal() << std::endl;  // Ausgabe: Bla
    

    Vielleicht ist mein Ansatz ja auch zu kompliziert? Wie könnte ich es eleganter lösen?



  • Da reicht ja ein Zeiger auf den String. std::string verwaltet dir den Speicher von selbst und somit reicht ein Zeiger aus. Dann kannst du dort einen string setzen und auch resetten. (Ausser du willst dann auch einen Default-String unterstützen. Dann musst du wieder Speicher holen, aber ansonsten nicht.)

    class foo
    {
    std::string* str;
    
    public:
    foo ():str(0){}
    void set ( std::string* s ) { str = s; }
    
    void bar ()
    {
     if ( str )
      std::cout << *str;
    } 
    };
    

    Sowas in der Art.



  • dem stimme ich zu, voll und ganz.

    falls es allerdings unbedingt ein doppelter zeiger sein soll, klappt das hier bei mir:

    #include <iostream>
    #include <string>
    using std::cout;
    using std::endl;
    using std::string;
    
    void displayStringPtr( string **str );
    string **getPtr( const string &str );
    
    int main()
    {
    	string str( "wasn schwachfug das hier ist ..." );
    	displayStringPtr( getPtr( str ) );
    	return 0;
    }
    
    void displayStringPtr( string **str )
    {
    	cout << **str << endl;
    }
    
    string **getPtr( const string &str )
    {
    	return const_cast< string ** >( &( new string ( str ) ) );
    }
    

    ich wollts eigentlich nur mal probieren, wie gesagt, besonderen sinn sehe ich darin auch nicht ...

    mfg,
    julian


  • Administrator

    Julian__ schrieb:

    ..., klappt das hier bei mir:

    Nur weil es sich kompilieren lässt und vielleicht sogar korrekt abläuft, heisst noch nicht, dass der Code korrekt ist. Das ist garantiertes undefiniertes Verhalten, welches du da erzeugst!
    Du gibst die Adresse auf eine temporäre Variable weiter.
    Zudem erzeugst du ein Speicherleck.
    Und es ist ein weiterer Beweis, wieso ein const_cast auf einen Designfehler hinweist.

    Grüssli



  • Folgender Code macht leider nicht das was er soll(siehe Kommentar im Quelltext). Auf delete habe ich verzichtet, um den Fehler anschaulich zu halten.

    #include <iostream>
    #include <string>
    #include <vector>
    
    class Object
    {
    private:
    	std::string *val;
    
    public:
    	Object(std::string *v) : val(v)
    	{
    		;
    	}
    
    	~Object()
    	{
    		delete val;
    	}
    
    	void setVal(std::string *v)
    	{
    		val = v;
    	}
    
    	std::string *getVal()
    	{
    		return val;
    	}
    };
    
    int main()
    {
    	Object obj(new std::string("Test"));
    	std::cout << *obj.getVal() << std::endl;
    
    	Object ref(obj.getVal());
    	ref.setVal(new std::string("Noch ein Test"));
    
    	std::cout << *ref.getVal() << std::endl;
    	std::cout << *obj.getVal() << std::endl; 
    // In obj sollte nun auch "Noch ein Test" gespeichert werden, aber das wird es nicht... 
    // Ich brauche sehr wohl einen Doppelpointer, oder? Jemand eine bessere Lösung?
    }
    

    Vorschläge? 😃



  • Der Code macht sehr wohl, was er soll.

    Der Unterschied ist, dass du etwas anderes erwartest..

    Also zuerst einmal ist das miserables Design. Speicher sollte immer von dem Objekt verwaltet werden, dass es anfordert. Du erzeugst ausserhalb der Klasse den Zeiger und die Klasse ist dann verantwortlich für die Freigabe. Das ist völliger Mist so. Da du so sehr einfach Speicherlecks hast.

    Du willst eher so etwas:

    class A
    {
     std::string& str;
    public:
     A ( std::string& s )
     :
     str ( s )
     {}
    
     void set (std::string s )
     {
      str = s;
     }
     std::string& get () const
     {
      return str;
     }
    };
    
    int main ()
    {
     std::string s = "Hallo du da";
    
     A a ( s );
     std::cout << a.get () << std::endl;
    
     A b ( a.get () );
     std::cout << b.get () << std::endl;
    
     b.set ( "hmm" );
    
     std::cout << a.get () << std::endl;
     std::cout << b.get () << std::endl;
    }
    

    Das könntest du auch mit Zeigern lösen, finde ich aber unnötig kompliziert.

    Es wäre möglich, dass du nicht unbedingt einen string von ausserhalb möchtest (aus der main), sondern einen, der die Klasse selbt verwaltet, dann müsste man es mit dynamisch angefordertem Speicher machen. (oder mit einem static string).

    Auf jeden Fall finde ich ein solches Design fragwürdig, wo die eine Klasse auf eine andere Einfluss hat. Es ist nicht unmöglich, dass so etwas gebraucht wird, aber sehr unwahrscheinlich.

    Ich habe irgendwie das Gefühl, dass du von C# kommst, kann das sein? Wenn ja, dann solltest du dich noch einmal ein wenig mit Speicherverwaltung in C++ beschäftigen, denn das ist nicht ganz gleich, wie in C#.



  • drakon schrieb:

    Das könntest du auch mit Zeigern lösen, finde ich aber unnötig kompliziert.

    Es wäre möglich, dass du nicht unbedingt einen string von ausserhalb möchtest (aus der main), sondern einen, der die Klasse selbt verwaltet, dann müsste man es mit dynamisch angefordertem Speicher machen. (oder mit einem static string).

    Auf jeden Fall finde ich ein solches Design fragwürdig, wo die eine Klasse auf eine andere Einfluss hat. Es ist nicht unmöglich, dass so etwas gebraucht wird, aber sehr unwahrscheinlich.

    Ich habe irgendwie das Gefühl, dass du von C# kommst, kann das sein? Wenn ja, dann solltest du dich noch einmal ein wenig mit Speicherverwaltung in C++ beschäftigen, denn das ist nicht ganz gleich, wie in C#.

    Nein, komme nicht von C# 😃 Ich probiere mal noch ein bisschen, ob der Code jetzt endlich das macht, was ich erwarte. Wenn dem so ist, dann habe ich wohl die gesuchte Lösung 🙂

    Sieht bis jetzt aber sehr gut aus 🙂 Danke für die Hilfe 👍



  • Ein Problem habe ich dann doch noch 😃

    Ich brauche nicht in allen Fällen einen Referenz. Gibt es eine Möglichkeit das je nach Flag auch nur eine Kopie angelegt wird? Das wäre genial 🙂



  • HelpMe... schrieb:

    Ein Problem habe ich dann doch noch 😃

    Ich brauche nicht in allen Fällen einen Referenz. Gibt es eine Möglichkeit das je nach Flag auch nur eine Kopie angelegt wird? Das wäre genial 🙂

    Das kannst du von ausserhalb bestimmen. Leg halt eine Kopie an und initialisiere den mit dem Rückgabewert vom anderen Objekt.

    Analog zu meinem Beispiel von oben

    ..
    std::string s2 ( a.get () );
    b.set ( s2 );
    ..
    


  • Gut zu wissen 🙂 Vielen Dank. Aber hat es eigentlich einen Grund warum ich operator= in meiner Klasse haben muss, wenn ich die Klasse A in einen std::vector speichern will? Habe ich opertor= nicht in meiner Klasse kompiliert es nicht.

    Habe jetzt einfach das geschrieben:

    A& operator=(const A&)
    {
         return *this;
    }
    

    Wäre das so in Ordnung? 🙂



  • Deine Klasse benötigt einen Kopierkonstruktor, um kompatibel zum std::vector zu sein.
    Wenn dein Kopierkonstruktor aber nichts kopiert, wirst du nach ein paar Container-vergrößerungen (im Falle von vector zB.) mit lauter "leeren" membern dastehen, weil die Container ja unter umständen den gesamten Inhalt woanders hinkopieren müssen.
    Ein operator=, der nichts kopiert, ist nebenbei auch etwas unintuitiv. Kann es sein, dass dein Kopierkonstruktor den operator= verwendet?



  • Noch was zu den Speicherlecks: Wenn du solches Design unbedingt haben musst, bieten sich immer noch Smart Pointer an, die sicherstellen, dass der angeforderte Speicher freigegeben wird.


Anmelden zum Antworten