Temporäres Objekt modifizieren & ref. zurückgeben..



  • Mir fällt in deiner Funktion kein Fehler oder Problem auf. Deinen Aufruf lässt allerdings nur VS durch, ein Standardkonformer Compiler lässt hier kein temporäres Objekt zu.

    Ich möchte, dass diese Funktion so aufgerufen werden kann, dass der Parameter direkt verändert wird, aber auch zurückgegeben wird.
    Doch sie soll auch für konstante Objekte aufgerufen werden können..

    Selbstverständlich kannst du eine Konstante nicht verändern, deine Anforderung ist also unsinnig.



  • codebase! schrieb:

    int main()
    {
    	const std::wstring ws = L"C:\\dir\\file.dat";
    
    	std::wcout << ExtractFileName(std::wstring(ws)); // hmm
    }
    

    Dein Hauptproblem ist ein anderes: die Funktion ist soweit in Ordnung. Was du hinter dem wcout machst, ist falsch: du erzeugst ja eine nicht-const Kopie des Strings, deren Lebenszeit außerhalb von ExtractFileName() jedoch nicht garantiert ist, und wcout ggf. auf illegalen Speicher zugreift.
    Wenn du das Objekt vorher erzeugst, lebt es noch bei wcout.

    Der sematisch richtigere Weg wäre eher, dass ExtractFileName() die Kopie selbst erzeugt und zurückgibt, dann wird auch der Aufruf klarer. Warum? Der Name "extract" suggeriert, dass der ursprüngliche Dateipfad zumindest in Teilen irgendwo weiterlebt.



  • minastaros schrieb:

    Was du hinter dem wcout machst, ist falsch: du erzeugst ja eine nicht-const Kopie des Strings, deren Lebenszeit außerhalb von ExtractFileName() jedoch nicht garantiert ist, und wcout ggf. auf illegalen Speicher zugreift.

    Ok, genau das war nämlich meine Befürchtung...

    Also dann gebe ich besser eine Kopie zurück, und wenn ich die Funktion mit einem konstanten String/Referenz aufrufen will, muss ich eben "std::wstring(ws)" übergeben, ansonsten eben nur den nicht-konstanten String.



  • Warum nicht zwei Funktionen? Eine, die eine Kopie macht und die andere, die in-place ändert. Das sorgt für viel weniger Verwirrung.

    void inplace(std::wstring& s)
    {
       ...
    }
    
    std::wstring copy(std::wstring s)
    {
        inplace(s);
        return s;
    }
    


  • codebase! schrieb:

    ... muss ich eben "std::wstring(ws)" übergeben, ansonsten eben nur den nicht-konstanten String.

    Nee, genau das nicht! Du kannst kein temporäres Objekt allein als Funktionsargument übergeben, weil es nur innerhalb der beiden Klammern vom ExtractFileName()-Aufruf lebt und außerhalb nicht mehr. Eher sowas:

    std::wstring ExtractFileName(std::wstring filePathName)  // hier wird kopiert (ohne &)
    {
        return filePathName.erase(0, filePathName.find_last_of(L'\\') + 1);
    }
    
    int main()
    {
        const std::wstring ws = L"C:\\dir\\file.dat";
    
        std::wcout << ExtractFileName(ws);    // geht, weil die Funktion eine Kopie erzeugt - und _diese_ kürzt
    }
    

    oder ein temporäres Objekt _vor_ dem wcout:

    std::wstring& ExtractFileName(std::wstring& filePathName)
    {
        return filePathName.erase(0, filePathName.find_last_of(L'\\') + 1);
    }
    
    int main()
    {
        const std::wstring ws = L"C:\\dir\\file.dat";
    
        std::string nonConstStr(ws);    // hier ist das temporäre Objekt - aber es überlebt das wcout! 
        std::wcout << ExtractFileName(nonConstStr);
    }
    

    Oder eben Nexus' Ansatz...



  • minastaros schrieb:

    Du kannst kein temporäres Objekt allein als Funktionsargument übergeben, weil es nur innerhalb der beiden Klammern vom ExtractFileName()-Aufruf lebt und außerhalb nicht mehr.

    Genau genommen lebt es während des ganzen Ausdrucks, also bis zum Semikolon. Das kann einen Unterschied machen, weil z.B.

    OtherFunction(ExtractFileName(temporary));
    

    funktioniert,

    std::wstring& s = ExtractFileName(temporary);
    OtherFunction(s);
    

    hingegen nicht (mal ausser Acht gelassen, dass man eh keine Temporaries an Non-Const-LValue-Referenzen binden kann).

    Die Situation ist mir in der Praxis vor allem mit c_str() begegnet, wo ich mich immer gefragt habe, wie lange der lebt, wenn der String dahinter selbst temporär ist.



  • codebase! schrieb:

    also ich weiß ja, dass man temporäre Objekte eigentlich nicht per Referenz zurückgeben soll.
    Nur ist dieser Fall vielleicht eine Ausnahme?

    Wenn der Code bei Dir überhaupt so kompiliert, dann nutzt Du wahrscheinlich ein "Feature" von Microsoft aus, was Dir das Initialisieren einer Lvalue-Referenz mit einem temporären Objekt erlaubt; denn das ist kein Standard-C++. In Standard-C++ ist das absichtlich nicht erlaubt.

    Abgesehen davon, wäre es nur dann eine Ausnahme, wenn Du mit diesem Ansatz wirklich etwas gewinnen würdest. Ich sehe aber nur die Gefahr des Missbrauchs. Zum Beispiel wird das hier

    for (auto c : ExtractFileName(std::wstring(ws))) {
        cout << c;
    }
    cout << endl;
    

    in Deinem Fall undefiniertes Verhalten hervorrufen, weil das temporäre wstring -Objekt nicht lang genug lebt. Einen Vorteil sehe ich nicht. Du hättest auch einfach das hier schreiben können:

    std::wstring ExtractFileName(std::wstring filePathName)
    {
        filePathName.erase(0, filePathName.find_last_of(L'\\') + 1);
        return filePathName;
    }
    

    Man beachte: Weder Rückgabe noch Parameter ist eine Referenz. Außerdem habe ich hier absichtlich das return vom erase getrennt, weil so das wstring -Objekt implizit "gemoved" wird. Alternativ hätte man dafür std::move benutzen müssen.

    Im Vergleich zu Deiner Lösung findet hier nur ein zusätzliches Move statt. Und das ist bei wstring -Objekten billig.

    Vielleicht kann ich Dich ja auch für die Programmiersprache Rust begeistern. In Rust ist die Lebenszeit ein Teil des Typsystems. In Rust kann man problemlos Referenzen aus Funktionen zurück geben. Sie müssen nur entsprechend um eine bestimmte Lebenszeitgarantie erweitert werden:

    fn extract_filename<'a>(s: &'a str) -> &'a str {
       ...
    }
    

    Hier ist 'a ein Lebenszeitparameter und der Typ &str ein String-Slice (effektiv Pointer + Länge). Die Funktion verspricht, ein String-Slice zurück zu geben, was mindestens genauso lange gültig bleibt, wie das übergebene String-Slice. Somit werden "use-after-free"-Fehler zur Compilezeit in Rust ausgeschlossen. Du würdest einfach einen Übersetzungsfehler statt undefiniertes Laufzeitverhalten bekommen. Aber die Sprache ist noch sehr experimentell. Da ändert sich auch gern mal was an der Sprache und/oder Standardbibliothek.



  • Nexus schrieb:

    Genau genommen lebt es während des ganzen Ausdrucks, also bis zum Semikolon. Das kann einen Unterschied machen, weil z.B.

    OtherFunction(ExtractFileName(temporary));
    

    funktioniert

    Ah, also hieße das, dass prinzipiell die zu Beginn vom TE erstellte Form sogar funktionieren dürfte? :

    std::wcout.operator<<( ExtractFileName(std::wstring(ws)));
    

    Da hab ich wieder was gelernt, danke. Nichtsdestotrotz würde ich diese Form nicht verwenden, weil alternative Formen klarer sind und die Frage gar nicht erst aufwerfen.
    Das im Funktionsaufruf notwendige std::wstring() fühlt sich einfach fehl am Platze an. Dann lieber eine Funktion, die sich auch ohne Krücke sauber aufrufen lässt, also entweder eine eigene Funktion oder eine explizite non-const-Kopie vorher.



  • minastaros schrieb:

    Ah, also hieße das, dass prinzipiell die zu Beginn vom TE erstellte Form sogar funktionieren dürfte? :

    std::wcout.operator<<( ExtractFileName(std::wstring(ws)));
    

    Jein. Was NICHT geht: Man kann eine Lvalue-Referenz-auf-nicht-const nicht mit einem temporären Objekt initialisieren. Das ist bzgl Standard-C++ ein Fehler, der zur Compilezeit von jedem vernünftigen Compiler nicht akzeptiert wird.

    Aber das temporäre Objekt würde lang genug leben, ja. Eben so lange bis der komplette Ausdruck ausgewertet wurde (quasi beim Semikolon).



  • kkaw schrieb:

    Wenn der Code bei Dir überhaupt so kompiliert, dann nutzt Du wahrscheinlich ein "Feature" von Microsoft aus, was Dir das Initialisieren einer Lvalue-Referenz mit einem temporären Objekt erlaubt; denn das ist kein Standard-C++. In Standard-C++ ist das absichtlich nicht erlaubt.

    kkaw schrieb:

    Das ist bzgl Standard-C++ ein Fehler, der zur Compilezeit von jedem vernünftigen Compiler nicht akzeptiert wird.

    Um mal dem Halbwissen entgegen zu wirken: Bei Visual Studio ist das eine Compilererweiterung, die bei entsprechender Warnstufe eine Warnung hervorruft und sich abschalten lässt.

    Etwa ähnlich, wie g++ Variable-Length-Arrays und andere Dinge erlaubt, solange man diese nicht ausdrücklich mit Compilerflags deaktiviert.


Anmelden zum Antworten