Zurückgabe einer neuen Klassen-Instanz aus statischer Funktion / new oder nicht?



  • Hallo zusammen,

    mir ist (als Anfänger) gerade nicht klar wie hier die korrekte Vorgehensweise ist:

    Ich habe eine Klasse "Song", diese beinhaltet u.a. eine statische Funktion "load", mit der ich die Daten für eine neue Instanz von der Festplatte lade, ist das Laden erfolgreich, so gibt die Funktion eine neue Instanz der Klasse "Song" an den Aufrufer zurück.

    Meine Frage ist nun wie ich diese Instanz korrekt erzeuge, ich bin zunächst mal davon ausgegangen, dass ich sie mit new erstellen muss, weil ich sie ja nicht nur im Scope der load-Function benötige sondern darüber hinaus.

    Ich habe beide Varianten probiert, einmal mit new und einmal ohne - und beides scheint zu funktionieren, aber warum funktioniert Variante 1?

    Erkennt der Compiler hier selbstständig, dass die neue Instanz eben nicht auf dem Stack sondern im Heap abgelegt werden muss?

    class Song
    {
    	static Song load1(string fileName, LOADSONGERROR& error);
    	static Song* load2(string fileName, LOADSONGERROR& error);
    }
    
    Song Song::load1(string fileName, LOADSONGERROR& error)
    {
    	Song song;
    	// Daten werden hier geladen und ggfls. error gesetzt
    	return song;
    }
    // delete &song;
    
    Song* Song::load2(string fileName, LOADSONGERROR& error)
    {
    	Song* pSong = new Song();
    	// Daten werden hier geladen und ggfls. error gesetzt
    	return pSong;
    }
    // delete song;
    

    Vielen Dank für alle Antworten im voraus.



  • Nein, du gibst eine Kopie zurück. Beid er Zweiten Alternative gibst du das identische Objekt zurück.

    Variante 2 ist jedoch nicht zu empfehlen. Genauso wie du delete &song nicht brauchst.



  • Erzeugst du eine lokale Variable innerhalb einer Funktion auf dem Stack und gibst sie zurück, wird die Variable in das Rückgaberegister kopiert (seit C++11 gemoved), die lokale Variable wird zerstört und der Wert aus dem Rückgaberegister anschließend in das Ziel kopiert (gemoved).
    Bei

    song s = song::load1(...);
    

    Enthält s nun eine Kopie von der lokalen Variable.
    Die Rückgabe von lokalen Variablen ist nur dann gefährlich, wenn du eine Referenz zurückgibst. Denn dann wird keine Kopie erstellt, sondern ein Verweis auf etwas schon zerstörtes zurückgegeben.



  • Danke euch beiden, dass im ersten Fall eine Kopie zurück gegeben wird habe ich nicht bedacht 🙂

    Das delete habe ich jeweils im Kommentar druntergeschrieben, als Andeutung darauf, dass ich das Objekt ja irgendwann auch wieder verwerfen muss.

    Die aufrufende Klasse hält das geladene Song-Objekt solange wie es benötigt wird. Muss ich das denn nicht explizit mit delete &song löschen sobald ich es nicht mehr benötige?

    (Mir ist klar das es SmartPointer gibt, aber ich möchte erst einmal die Grundlagen verstehen).



  • void foo()
    {
      int i;
      i = 42;
      // muss ich i hier deleten?
    }
    


  • Nein, weil i automatisch verworfen wird sobald die Funktion verlassen wurde.

    Aber ich möchte das Song-Objekt ja in der aufrufenden Klasse eine Weile im Kontext der gesamten Klasse als privaten Member behalten (z.B. Song m_song) und nicht nur solange ich in einer Funktion bin die Song.Load() aufruft. Sobald ich den nächsten Song lade müsste ja der alte Song verworfen werden. Oder geschieht dies durch überschreiben von m_song auch automatisch?



  • Was mein Beispiel zeigen wollte: Du musst Objekte die auf dem Stack liegen nie deleten, darfst es sogar nicht!
    Und eine Klasse kann keine Membervariablen vom eigenem Typ haben!
    Edit: BTW: Zur Erzeugung von Klassen nutzt man aber idR Konstruktoren, nicht statics.



  • Klar benutzt man normalerweise Konstruktoren. Allerdings ist mein Beispiel hier stark vereinfach dargestellt, ein Song besteht aus ganz vielen Unterobjekten und deren Chunks in der Datei (ich hätte das in meiner Eingangsfrage wahrscheinlich genauer erklären müssen) 🙂

    Darum möchte ich das jedes Objekt, angefangen beim Song-Objekt, sich selbst laden und speichern kann und über eine statische Funktion einfach ein fertig geladenes Objekt von sich selbst zurückgibt.

    Der Aufrufer von Song.load() könnte z.B. jetzt eine Klasse SongEditor sein. Diese speichert dann in einer privaten Member-Variable m_song den fertig geladenen Song und kann dann von SongEdtor-Funktionen bearbeitet werden. In der load-Funktion von Song wiederum werden die ganzen Unterobjekte auf dieselbe Weise geladen und gehalten (beispielsweise die Tracks des Songs sowie deren Noten als weitere Unterkategorie an Objekten).

    Für den Stack ist ein gesamtes Song-Objekt natürlich zu groß. Also werde ich sicherlich doch die Variante 2 nehmen müssen und nur den Zeiger auf das Song-Objekt zurückgeben müssen? Und mich natürlich später auch um das löschen des Songs-Objektes nebst seiner Unter-Objekte selber kümmern müssen?



  • Das ist kein Problem, das m_song Objekt bleibt so lange auf dem Stack wie das SongEditor Objekt am Leben bleibt.
    Wie groß ist denn dein Song-Objekt (sizeof)? Bei vierstelligen Werten würd ich mir anfangen Gedanken zu machen.



  • Da ich das Projekt gerade erst von meiner C# Version nach C++ übersetze kann ich die genaue Größe mit sizeof() noch gar nicht bestimmen. Das Song Objekt hält aber wie gesagt viele Unterobjekte, die u.a. auch Sampling-Daten halten können, im absoluten Worst-Case dürften das so bis zu 256 MB sein, schätze ich. Darum dachte ich halt eben das das keinesfalls auf den Stack darf sondern in den Heap muss. Aber da fehlt mir eben noch das Hintergrundwissen in C++, in C# ist das alles doch etwas einfacher 🙂



  • Bojana schrieb:

    Klar benutzt man normalerweise Konstruktoren. Allerdings ist mein Beispiel hier stark vereinfach dargestellt, ein Song besteht aus ganz vielen Unterobjekten und deren Chunks in der Datei (ich hätte das in meiner Eingangsfrage wahrscheinlich genauer erklären müssen) 🙂

    Nein eigentlich nicht, all das Gesagte gilt auch für Unterobjekte, Unterunterobjekte, Unterunterunterobjekte usw.

    Bojana schrieb:

    Darum möchte ich das jedes Objekt, angefangen beim Song-Objekt, sich selbst laden und speichern kann und über eine statische Funktion einfach ein fertig geladenes Objekt von sich selbst zurückgibt.

    Warum?

    Bojana schrieb:

    Der Aufrufer von Song.load() könnte z.B. jetzt eine Klasse SongEditor sein. Diese speichert dann in einer privaten Member-Variable m_song den fertig geladenen Song und kann dann von SongEdtor-Funktionen bearbeitet werden. In der load-Funktion von Song wiederum werden die ganzen Unterobjekte auf dieselbe Weise geladen und gehalten (beispielsweise die Tracks des Songs sowie deren Noten als weitere Unterkategorie an Objekten).

    Das führt am eigentlichen Sachverhalt komplett vorbei. Beim erstellen eines Objektes wird immer ein Kosntruktor aufgerufen, da kann man es auch gleich richtig machen.

    Bojana schrieb:

    Für den Stack ist ein gesamtes Song-Objekt natürlich zu groß. Also werde ich sicherlich doch die Variante 2 nehmen müssen und nur den Zeiger auf das Song-Objekt zurückgeben müssen? Und mich natürlich später auch um das löschen des Songs-Objektes nebst seiner Unter-Objekte selber kümmern müssen?

    Man kann auch mit Stackobjekten Heapobjekte verwalten (siehe std::vector)


Anmelden zum Antworten