5 Stimmen

Geben Sie ein großes Struktur-Objekt durch Wert oder Zeiger zurück, um eine dynamische Allokation in C++ zu ermöglichen

Hier ist mein Problem. Ich habe eine Funktion, die struct als Eingabe nimmt, eine neue struct allokiert und sie dann zurückgibt. Die struct hat folgenden Inhalt

struct Data {
std::vector vector_vars;
std::string key;
std::map index;
};

vector_vars hat eine Größe von 500-1000. custom_type ist relevant diese Klasse. Der index hilft mir, auf verschiedene Data Structuren über key zuzugreifen. Dies ist die Funktion.

Data func(Data inputData) {

 Data outputData;
//Vergleiche Werte mit inputData
//Ändere einige Werte in Data struct
 return outputData
}
  1. Benutze ich Stack-Allokation und gebe es zurück, damit es in die RHS kopiert wird. Ist dies ratsam, da das Kopieren möglicherweise viele Überkopfdaten erzeugt?

  2. Kann ich eine Referenz/Zeiger auf die innerhalb einer Funktion allokierte Struktur zurückgeben, die auf dem Stack liegt?

  3. Ist es ratsam, hier stattdessen dynamische Allokation zu verwenden (vielleicht mit std::shared_ptr) für Effizienz?

14voto

Joseph Mansfield Punkte 104275
  1. Geben Sie das Objekt als Wert zurück. Die Copy-Elision ist eine Optimierung, die es dem Standard erlaubt, Compiler zu entfernen, die unnötige Kopien entfernt. In C++03 wird die Kopie vom Compiler fast sicher eliminiert. In C++11 wird die Kopie zunächst als Move betrachtet (was wiederum seine Inhalte bewegt) und selbst das kann eliminiert werden.

    in einem return-Anweisung in einer Funktion mit einer Klasserückgabetyp, wenn der Ausdruck der Name eines nicht-volatilen automatischen Objekts (außer einem Funktions- oder Catch-Klausel-Parameter) mit demselben cv-unqualifizierten Typ wie der Funktion-Rückgabetyp ist, kann die Kopier-/Verschiebeoperation durch direktes Konstruieren des automatischen Objekts direkt in den Rückgabewert der Funktion ausgelassen werden.

    Speziell, wenn diese Kriterien zu einer Copy-Elision führen, ist dies allgemein als Named Return Value Optimization bekannt, eine Form der Rückgabewert-Optimierung.

    Das idiomatischste und leistungsstärkste Option hier ist also die Rückgabe als Wert. Natürlich, wenn Sie messsen, dass selbst unter Optimierung die Rückgabe als Wert ein Problem ist, dann sollten Sie sicherlich in Erwägung ziehen, dynamisch zu allozieren (siehe Punkt 3).

  2. Nein, Sie sollten keine Referenz oder Zeiger auf eine lokale Variable zurückgeben. Das Objekt mit automatischer Speicherhaltung (was Sie als Allokation auf dem Stapel bezeichnen) wird nicht mehr existieren. Die Referenz oder der Zeiger wird hängen gelassen werden. Wenn Sie es auf irgendeine sinnvolle Weise verwenden, wird dies zu undefiniertem Verhalten führen.

  3. Nein, wie oben erwähnt, ist es ratsam, als Wert zurückzugeben. Wenn Sie jedoch wirklich, wirklich müssen, können Sie Ihr Data dynamisch zuweisen und es durch einen Smart-Zeiger zurückgeben. Der bevorzugte Smart-Zeiger hier wäre ein std::unique_ptr, da Ihre Funktion das Eigentum an die aufrufende Funktion übergibt (nicht das Eigentum teilt).

2voto

Mankarse Punkte 38418
  1. Eigentlich könnten hier zwei schnelle Operationen stattfinden: Entweder wird outputData in den Rückgabewert verschoben oder dieser Schritt wird vermieden. In keinem Fall werden teure Kopien erstellt. Die Kopie von inputData scheint unnötig zu sein. Vielleicht sollten Sie es als Data const& inputData übergeben.
  2. Sie könnten dies tun, aber es würde zu einem undefinierten Verhalten führen.
  3. Nein. Die dynamische Speicherzuweisung ist wahrscheinlich weniger sicher, langsamer, verwirrender und im Allgemeinen schlechter.

2voto

LihO Punkte 39378
Daten func1(Daten inputData) {
    Daten outputData;
    return outputData
}

nimmt das Objekt vom Typ Daten per Wert entgegen und erstellt eine weitere Instanz von Daten, die dann per Wert zurückgegeben wird. Die Kopie wird nicht nur erstellt, wenn outputData zurückgegeben wird, sondern auch wenn inputData empfangen wird. Machen Sie sich keine Sorgen um die Leistung bei der Rückgabe per Wert, es gibt Mechanismen, die sich darum kümmern (wert zu erwähnen sind Kopaelision und mögliche Rückgabewertoptimierung). Ich empfehle jedoch, inputData per Referenz zu übergeben: Daten func1(const Daten& inputData).

Ist das ratsam, da das Kopieren viele Overheads erzeugen kann?

Daumenregel: Machen Sie sich keine Sorgen über die Leistung Ihres Codes, es sei denn, Sie haben Leistungsprobleme.

Kann ich eine Referenz/einen Zeiger auf eine Struktur zurückgeben, die im Funktionskontext auf dem Stapel allokiert wurde?

Geben Sie keine Referenz / Zeiger auf das Objekt mit automatischer Speicherdauer zurück, das innerhalb des Bereichs dieser Funktion definiert ist. Das Objekt wird zerstört, sobald die Ausführung den Bereich verlässt. Sie erhalten also einen hängenden Zeiger (ungültige Referenz), was zu einem undefinierten Verhalten führt.

Ist es ratsam, hier stattdessen eine dynamische Allokation zu verwenden, um effizienter zu sein?

Vermeiden Sie dynamische Allokationen, wo immer möglich. Nicht wegen der Effizienz, sondern wegen der Speicherverwaltung in Ihrem Code. Befolgen Sie das RAII-Prinzip und vermeiden Sie die Handhabung der unübersichtlichen Speicherverwaltung auf eigene Faust.

1voto

David Seiler Punkte 9491

Sie könnten func() umschreiben, um anstelle eines Data einen Data* zurückzugeben. Sicherlich ist es viel schneller, ein Data* zu kopieren als ein Data. Aber davor: Ist es Ihnen wichtig? Wird diese Funktion nur einmal während der Programminitialisierung ausgeführt und benötigt dabei eine Zehntelsekunde der Wanduhrzeit oder erwarten Sie, sie einmal pro Frame aufzurufen?

Wenn Sie ein Programm schreiben oder ein bestehendes optimieren, sollte Ihr Ziel immer sein, es "schnell genug" zu machen (und "schnell genug reagieren", aber das spielt jetzt keine Rolle). Die Definition von "schnell genug" variiert stark: In einer Simulation könnten Sie zufrieden sein, eine Terabyte Daten pro Stunde zu verarbeiten, während Sie bei einem Videospiel dem Spieler alle 16 Millisekunden ein neues Grafikbild zeigen müssen.

Also: Ist Ihr Programm "schnell genug" mit nur Stapelspeicherzuweisung? Wenn ja, lassen Sie es so. Falls nicht, verwenden Sie stattdessen eine dynamische Zuweisung. Im Allgemeinen ist es guter C++-Stil, Zeiger anstelle großer Strukturen zurückzugeben, aber darüber sollten Sie sich keine Gedanken machen, solange Sie noch am Lernen sind. Machen Sie es funktionierend, machen Sie es richtig, dann machen Sie es schnell.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X