Ich weiß, dass der Titel bekannt vorkommt, da es viele ähnliche Fragen gibt, aber ich frage nach einem anderen Aspekt des Problems (ich kenne den Unterschied zwischen dem Ablegen von Dingen auf dem Stapel und dem Ablegen auf dem Haufen).
In Java kann ich immer Verweise auf "lokale" Objekte zurückgeben
public Thing calculateThing() {
Thing thing = new Thing();
// do calculations and modify thing
return thing;
}
In C ++ habe ich zwei Möglichkeiten, um etwas Ähnliches zu tun
(1) Ich kann Referenzen verwenden, wenn ich ein Objekt "zurückgeben" muss
void calculateThing(Thing& thing) {
// do calculations and modify thing
}
Dann benutze es so
Thing thing;
calculateThing(thing);
(2) Oder ich kann einen Zeiger auf ein dynamisch zugewiesenes Objekt zurückgeben
Thing* calculateThing() {
Thing* thing(new Thing());
// do calculations and modify thing
return thing;
}
Dann benutze es so
Thing* thing = calculateThing();
delete thing;
Mit dem ersten Ansatz muss ich den Speicher nicht manuell freigeben, aber für mich ist der Code schwer zu lesen. Das Problem mit dem zweiten Ansatz ist, dass ich mich daran erinnern muss delete thing;
, was nicht ganz gut aussieht. Ich möchte keinen kopierten Wert zurückgeben, weil er ineffizient ist (glaube ich). Hier kommen die Fragen
- Gibt es eine dritte Lösung (für die kein Kopieren des Werts erforderlich ist)?
- Gibt es ein Problem, wenn ich mich an die erste Lösung halte?
- Wann und warum sollte ich die zweite Lösung verwenden?
quelle
Antworten:
Beweise es.
Suchen Sie nach RVO und NRVO sowie in C ++ 0x Verschiebungssemantik. In den meisten Fällen in C ++ 03 ist ein out-Parameter nur ein guter Weg, um Ihren Code hässlich zu machen, und in C ++ 0x würden Sie sich tatsächlich selbst verletzen, wenn Sie einen out-Parameter verwenden.
Schreiben Sie einfach sauberen Code und geben Sie ihn nach Wert zurück. Wenn die Leistung ein Problem darstellt, profilieren Sie es (hören Sie auf zu raten) und finden Sie heraus, wie Sie es beheben können. Es wird wahrscheinlich keine Dinge von Funktionen zurückgeben.
Das heißt, wenn Sie fest entschlossen sind, so zu schreiben, möchten Sie wahrscheinlich den out-Parameter ausführen. Es vermeidet eine dynamische Speicherzuweisung, die sicherer und im Allgemeinen schneller ist. Es erfordert eine Möglichkeit, das Objekt vor dem Aufrufen der Funktion zu erstellen, was nicht immer für alle Objekte sinnvoll ist.
Wenn Sie die dynamische Zuordnung verwenden möchten, können Sie sie zumindest in einen intelligenten Zeiger einfügen. (Dies sollte sowieso die ganze Zeit gemacht werden.) Dann müssen Sie sich keine Gedanken über das Löschen von Dingen machen, die Dinge sind ausnahmesicher usw. Das einzige Problem ist, dass es wahrscheinlich langsamer ist als die Rückgabe nach Wert!
quelle
Erstellen Sie einfach das Objekt und geben Sie es zurück
Ich denke, Sie tun sich selbst einen Gefallen, wenn Sie die Optimierung vergessen und nur lesbaren Code schreiben (Sie müssen später einen Profiler ausführen - aber nicht voroptimieren).
quelle
Thing thing();
deklariert eine lokale Funktion und gibt a zurückThing
.Geben Sie einfach ein Objekt wie das folgende zurück:
Dadurch wird der Kopierkonstruktor für Things aufgerufen. Sie können dies also selbst implementieren. So was:
Dies kann etwas langsamer sein, ist aber möglicherweise überhaupt kein Problem.
Aktualisieren
Der Compiler wird wahrscheinlich den Aufruf des Kopierkonstruktors optimieren, sodass kein zusätzlicher Aufwand entsteht. (Wie Dreamlax im Kommentar ausgeführt hat).
quelle
Thing thing();
deklariert eine lokale FunktionThing
, die a zurückgibt. Außerdem erlaubt der Standard dem Compiler, den Kopierkonstruktor in dem von Ihnen vorgestellten Fall wegzulassen. Jeder moderne Compiler wird es wahrscheinlich tun.Haben Sie versucht, intelligente Zeiger zu verwenden (wenn Thing wirklich ein großes und schweres Objekt ist), wie auto_ptr:
quelle
auto_ptr
s sind veraltet; benutzeshared_ptr
oderunique_ptr
stattdessen.Eine schnelle Möglichkeit, um festzustellen, ob ein Kopierkonstruktor aufgerufen wird, besteht darin, dem Kopierkonstruktor Ihrer Klasse eine Protokollierung hinzuzufügen:
Rufen Sie an
someFunction
; Die Anzahl der Zeilen "Kopierkonstruktor wurde aufgerufen", die Sie erhalten, variiert zwischen 0, 1 und 2. Wenn Sie keine erhalten, hat Ihr Compiler den Rückgabewert optimiert (was zulässig ist). Wenn Sie nicht 0 erhalten Sie erhalten, und Ihr Copykonstruktor ist unverschämt teuer, dann nach alternativen Möglichkeiten suchen Instanzen von Ihren Funktionen zurückzukehren.quelle
Erstens haben Sie einen Fehler im Code, den Sie haben wollen
Thing *thing(new Thing());
, und nurreturn thing;
.shared_ptr<Thing>
. Deref es als ein Zeiger. Es wird für Sie gelöscht, wenn der letzte Verweis auf dasThing
Enthaltene nicht mehr gültig ist.quelle
Ich bin sicher, dass ein C ++ - Experte eine bessere Antwort finden wird, aber ich persönlich mag den zweiten Ansatz. Die Verwendung intelligenter Zeiger hilft bei dem Problem, zu vergessen,
delete
und wie Sie sagen, sieht es sauberer aus, als ein Objekt zuvor erstellen zu müssen (und es trotzdem löschen zu müssen, wenn Sie es auf dem Heap zuweisen möchten).quelle