char* aus funktionsrueckgabewert



  • Dass man von std::string keinen Zeiger bekommen kann, über den man den String modifizieren darf, ist bedauerlich.

    In vielen Fällen mann man den operator [] statt dessen verwenden, denn die einzelnen Zeichen in einem String zu verändern ist ja schliesslich nicht verboten.
    In vielen Fällen kann man aber auch nicht ausweichen, und muss kopieren.

    @com_ptr<T>
    Deine Funktion ist vollkommen korrekt. Sie verlangt bloss vom Aufrufer, dass dieser den Speicher mit delete [] wieder freigibt.
    Und natürlich vorausgesetzt, dass zwischen dem new[] und dem return keine Exceptions fliegen können. Sonst hättest du nämlich wirklich ein Leak.

    Die Variante von cooky451 ist vielleicht besser, da der Aufrufer dann entscheiden kann wie er den Speicher verwaltet.
    Nur solltest du die Funktion dann um einen Parameter erweitern, in dem der Aufrufer die Länge des Puffers übergibt. Um zu verhindern, dass die Funktion nen Pufferüberlauf macht.
    Natürlich muss man dann wieder irgendwie den Fall behandeln, dass der Puffer zu klein ist, was die Sache wieder etwas lästig macht. Vor allem auf Seite des Aufrufers, der dann u.U. dynamisch den Puffer vergrössern muss.

    com_ptr<T> schrieb:

    Waere folgende loesung mit std::string korrekt?

    const strC fun()
    {
      //calculate len
      const char *c = new char[len];
      // fill c
      string strC(c);
      delete [] c;
    
      return strC;
    }
    

    Wenn man den offensichtlichen Flüchtigkeitsfehler mal ignoriert ("const strC fun()" muss "std::string fun()" heissen), dann ja.
    Bzw. gibt es hier wieder das selbe Problem mit Exceptions.
    Gegenvorschlag:

    std::string fun()
    {
        // calculate len
    
        std::vector<char> buffer(len);
        const char* c = &buffer[0];
    
        // fill c
    
        return std::string(buffer.begin(), buffer.end());
    }
    

    Das ist das, was ich immer mache, wenn ich Strings von z.B. OS Funktionen zurückgegeben bekomme, die ich dann als std::string brauche.

    Man kann das ganze auch noch etwas optimieren, wenn man eine Obergrenze für "len" Kennt, die in den meisten Fällen ausreichen wird:

    std::string fun()
    {
        static size_t const stackBufferSize = 512;
    
        size_t len = ...;
    
        char const* c;
        char stackBuffer[stackBufferSize];
        std::vector<char> buffer;
    
        if (len <= stackBufferSize)
            c = stackBuffer;
        else
        {
            buffer.resize(len);
            c = &buffer[0];
        }
    
        // fill c
    
        return std::string(c, c + len);
    }
    

    Wenn man sowas häufiger braucht, kann man sich eine kleine Helper-Klasse basteln, wodurch man dann etwas in der Art schreiben kann:

    std::string fun()
    {
        size_t len = ...;
    
        SmartBuffer<char, 512> buffer(len);
        char const* c = buffer.begin();
    
        // fill c
    
        return std::string(buffer.begin(), buffer.end()); // oder gleich buffer.ToString()
    }
    

    EDIT: nachdem du aus einer Datei liest, vergiss die Optimierung. Der Dateizugriff ist um ein vielfaches langsamer, als die unnötige Speicheranforderung in std::vector<>. D.h. es zahlt sich nicht aus die wegzuoptimieren.



  • com_ptr<T> schrieb:

    1. Es wird ein pointer auf nicht existierenden speicher zurueckgegeben

    Nein, der Speicher wurde ja mit "new" angefordert. Wenn das fehlschlägt, wirft C++ IMHO eine bad_alloc-Exception.

    com_ptr<T> schrieb:

    1. Es entsteht ein leak, da niemals delete [] c aufgerufen wird.

    Der Aufrufer kann "delete" auf den Rückgabewert der Funktionen anwenden und würde den Speicher damit freigeben.

    Btw, funktionieren würde es so, allerdings bezweifle ich, dass dein Code dem entspricht, was gemeinhin als "guter C++-Stil" verstanden wird.
    ^^ Worum ich mich persönlich aber auch nie schere. 😃



  • Was für Daten liest du denn aus der Datei und was passiert danach mit dem String?



  • @314159265358979

    Na, ich lese ne binaere datei ein. Fakt ist, dass ich wenn ich mit obiger funktion
    die datei lade, seh ich, dass im debugger nen andrer string ist, als den, den die datei tatsaechlich beinhaltet. Also muss da was krumm sein.
    Ich konvertiere den zurueckgegebenen std::string wieder in einen const char* mittels std::string::c_str(), was hier aber nicht weiter von bedeutung ist. Denn der "falsche" wert ist ja bereits im std::string vorhanden.



  • Naja, ich bezweifle mal, dass du die Daten intern binär brauchst. Du wirst sie doch sicherlich in andere Datentypen wie ints, floats, doubles o.ä. zerlegen, oder? In diesem Fall könntest du dir den Umweg über den String nämlich sparen.



  • Im konkreten handelt es sich um
    diese: http://dl.dropbox.com/u/6559846/com_ptrT.bin
    Datei, die ich lesen moechte.

    Debugging zeigt, dass die datei ~1500 bytes enthaelt.
    Interessanterweise werden aber nur 21 gelesen (Laenge des std::strings).
    Gelesen wird: "DXBCB÷”=}¤óUý<)íœæ’".



  • @314159265358979
    Nein, natuerlich brauche ich sie in binaer, sonst wuerd ich sie ja nicht binaer lesen, und das noch in nen char*, oder? *hehe*



  • Enthält die Datei 0-Bytes? In diesem Fall würde der std::string nämlich nicht alles kopieren, wenn du den char const* ctor nimmst.



  • Enthält die Datei 0-Bytes? In diesem Fall würde der std::string nämlich nicht alles kopieren, wenn du den char const* ctor nimmst.

    Yep, das war auch meine 1. Vermutung. Leider kann ich das nicht sagen, da es eine generierte datei ist. Wie loese ich das Problem?



  • Indem du den Range-Konstruktor verwendest:

    std::string s(ptr, ptr + len);
    


  • Wenn das sowieso kein "Test-String" ist, dann wäre vermutlich std::vector<char> der passendere Rückgabewerttyp.
    Alternativ kannst du den Template-Konstruktor von std::string verwenden, der ein Iterator-Paar (begin, end) übergeben bekommt. Damit kann man in std::string auch wunderbar 0-Zeichen reintun.



  • Habe nun mal folgendes versucht:

    const std::string ShaderManager::loadCompiledShader(const std::string& name)
    {
    	ifstream in(name.c_str(), ios::in || ios::binary);
    
    	in.seekg(0, ios::end);
    	std::streamoff length = in.tellg();
    	in.seekg(0, ios::beg);
    
    	vector<char> buffer(length);	
    	in.read(&buffer[0], length);
    	in.close();
    
    	string strBuffer(buffer.cbegin(), buffer.cend());
    
    	return strBuffer;
    }
    

    Problem: Bereits das eingelesene ist "gecutted" (= nur 21 zeichen).
    Hat es jemand mal mit der geposteten datei versucht?



  • ios::in || ios::binary - da ist ein | zuviel.
    Flags verknüpft man mit | , nicht mit || .



  • @hustbaer:

    Danke, sehr guter Punkt.
    Geht nun! 🙂

    Vielen Dank nochmal an @hustbaer und 314159265358979!

    Hoffe es ist nicht <zu> tragisch, dass wir etwas vom Thema abgeschweift sind.
    Aber man sieht immer wieder, was fuer ein "riesen Rattenschwanz" hinter vermeintlich "einfachen" fragen liegt. 😃 Euch noch einen schoenen Abend!



  • Wie wärs mit dem:

    const std::string ShaderManager::loadCompiledShader(const std::string& name)
    {
      std::ifstream in(name.c_str(), ios::in | ios::binary);
      return std::string((std::istreambuf_iterator<char>(in)),
                         std::istreambuf_iterator<char>());
    }
    


  • 👍



  • 👍

    rattenschwanz schrieb:

    Wie wärs mit dem:

    const std::string ShaderManager::loadCompiledShader(const…
    

    Ich habe nur eine Sache anzumeckern: Das const beim Rückgabetyp sollte weg.

    (Wenn Du mir jetzt mit "Aber Scott Meyers schreibt, dass..." kommst, dann kann ich Dich beruhigen. Der würde das heute, zu Zeiten von C++2011 auch nicht mehr vorschlagen. Das gibt es sogar quasi Schwarz auf Weiß irgendwo in einer der englischen C++ Usenet Gruppen. Ich bin nur zu faul, es rauszusuchen…) 🙂

    Warum macht das const dort heute noch weniger Sinn als früher? Weil es "move semantics" verhindert und zu unnötigen Kopien führen kann.



  • krümelkacker schrieb:

    Ich habe nur eine Sache anzumeckern: Das const beim Rückgabetyp sollte weg.

    Die Funktionssignatur hab ich so von com_ptr<T> übernommen, ich selber würde das nie in eine Memberfunktion stecken.

    Aber kannst du mir erklären, wieso das const move semantics verhindert?

    Ich habs mit dem GCC getestet und das Objekt wird genau einmal erzeugt (also genau ein Destruktoraufruf). Schreibe ich return std::move(std::string(...)) hin, gibt es zwei Destruktoraufrufe (einmal Movekonstruktor), aber unabhängig vom const.



  • Der Move-Konstruktor sieht ganz einfach so aus:

    string::string(string&&);
    

    Und weil das keine Referenz-auf-const ist als Parameter, kann der move-ctor auch nicht verwendet werden, wenn Du ein "const string" als Quelle hast.

    Dass da u.U. "nur einmal" etwas erzeugt wird, kann daran liegen, dass der GCC "copy elisions" durchführt, sofern möglich -- also die Notwendigkeit eines Kopierens bzw Movens minimiert.


  • Mod

    Der Unterschied wird offenbar, wenn zugewiesen wird.
    (Man könnte auf die Idee kommen, einen Zuweisungsoperator der Form foo& operator(foo) zu verwenden, dann ist move immer möglich... seit der Einführung von noexcept ist das allerdings eine sehr schlechte Idee, da move dann möglicherweise Exceptions wirft, und die Standardbibliothek darauf allergisch reagieren könnte...

    Und auch ohne noexcept sollte eine Klasse mit so einem Zuweisungsoperator besser nicht als Member einer anderen Klasse eingesetzt werden, die kann dann nämlich ebenfalls nicht ordentlich gemoved werden.


Anmelden zum Antworten