Wie kann man ein Objekt in C ++ zurückgeben?

Ich weiß, dass der Titel vertraut klingt, da es viele ähnliche Fragen gibt, aber ich frage nach einem anderen Aspekt des Problems (ich kenne den Unterschied zwischen Dingen auf dem Stapel und sie auf den Haufen zu legen).

In Java kann ich immer Referenzen auf “lokale” Objekte zurückgeben

public Thing calculateThing() { Thing thing = new Thing(); // do calculations and modify thing return thing; } 

In C ++, um etwas ähnliches zu tun, habe ich 2 Optionen

(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 pointers 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; 

Beim 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, ich muss daran denken, delete thing; zu delete thing; , die nicht ganz nett aussieht. Ich möchte keinen kopierten Wert zurückgeben, weil es ineffizient ist (denke ich), also kommen hier die Fragen

  • Gibt es eine dritte Lösung (die das Kopieren des Werts nicht erfordert)?
  • Gibt es ein Problem, wenn ich bei der ersten Lösung bleibe?
  • Wann und warum sollte ich die zweite Lösung verwenden?

   

    Ich möchte keinen kopierten Wert zurückgeben, da dies ineffizient ist

    Beweise es.

    Nachschlagen RVO und NRVO, und in C ++ 0x Bewegung Semantik. 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 selbst verletzen, indem Sie einen out-Parameter verwenden.

    Schreiben Sie einfach sauberen Code und geben Sie den Wert zurück. Wenn die performance ein Problem ist, profilieren Sie es (hören Sie auf zu raten) und finden Sie, was Sie tun können, um es zu beheben. Es wird wahrscheinlich keine Dinge von functionen zurückgeben.


    Das heißt, wenn Sie so schreiben, würden Sie wahrscheinlich den Parameter out machen wollen. Es vermeidet eine dynamische Speicherzuweisung, die sicherer und in der Regel schneller ist. Es erfordert, dass Sie eine Möglichkeit haben, das Objekt vor dem Aufruf der function zu konstruieren, was nicht immer für alle Objekte sinnvoll ist.

    Wenn Sie die dynamische Zuordnung verwenden möchten, ist es am wenigsten sinnvoll, sie in einen intelligenten pointers zu setzen. (Dies sollte sowieso die ganze Zeit erledigt werden.) Dann müssen Sie sich nicht darum kümmern, etwas zu löschen, die Dinge sind ausnahmesicher, usw. Das einzige Problem ist, dass es wahrscheinlich langsamer ist als die Rückgabe als Wert trotzdem!

    Erstellen Sie einfach das Objekt und geben Sie es zurück

     Thing calculateThing() { Thing thing; // do calculations and modify thing return thing; } 

    Ich denke, Sie werden sich einen Gefallen tun, wenn Sie die Optimierung vergessen und einfach lesbaren Code schreiben (Sie müssen später einen Profiler ausführen – aber nicht voroptimieren).

    Gib einfach ein Objekt wie folgt zurück:

     Thing calculateThing() { Thing thing(); // do calculations and modify thing return thing; } 

    Dadurch wird der Kopierkonstruktor für Things aufgerufen, sodass Sie möglicherweise Ihre eigene Implementierung durchführen möchten. So was:

     Thing(const Thing& aThing) {} 

    Dies kann etwas langsamer sein, aber es ist möglicherweise kein Problem.

    Aktualisieren

    Der Compiler wird wahrscheinlich den Aufruf des Kopierkonstruktors optimieren, so dass kein zusätzlicher Aufwand entsteht. (Wie dreamlax im Kommentar darauf hingewiesen).

    Haben Sie versucht, intelligente pointers zu verwenden (wenn Thing wirklich ein großes und schweres Objekt ist), wie auto_ptr:

     std::auto_ptr calculateThing() { std::auto_ptr thing(new Thing); // .. some calculations return thing; } // ... { std::auto_ptr thing = calculateThing(); // working with thing // auto_ptr frees thing } 

    Eine schnelle Möglichkeit, festzustellen, ob ein Kopierkonstruktor aufgerufen wird, besteht darin, dem Kopierkonstruktor der class Protokollierung hinzuzufügen:

     MyClass::MyClass(const MyClass &other) { std::cout < < "Copy constructor was called" << std::endl; } MyClass someFunction() { MyClass dummy; return dummy; } 

    Rufen Sie someFunction ; Die Anzahl der "Copy Konstruktor wurde genannt" Zeilen, die Sie erhalten, wird zwischen 0, 1 und 2 variieren. Wenn Sie keine erhalten, dann hat Ihr Compiler den Rückgabewert out (was es tun darf) optimiert. Wenn Sie keine 0 erhalten und Ihr Kopierkonstruktor lächerlich teuer ist, suchen Sie nach alternativen Möglichkeiten, Instanzen aus Ihren functionen zurückzugeben.

    Erstens hast du einen Fehler im Code, du meinst Thing *thing(new Thing()); und nur return thing; .

    • Verwenden Sie shared_ptr . Deref es als wäre es ein pointers. Es wird für Sie gelöscht, wenn der letzte Verweis auf das enthaltene Thing Gültigkeitsbereich verlässt.
    • Die erste Lösung ist in naiven Bibliotheken sehr verbreitet. Es hat etwas performance und syntaktischen Overhead, vermeiden Sie es wenn möglich
    • Verwenden Sie die zweite Lösung nur, wenn Sie garantieren können, dass keine Ausnahmen ausgetriggers werden oder wenn die performance absolut kritisch ist (Sie werden mit C oder der Assembly interagieren, bevor dies überhaupt relevant wird).

    Ich bin sicher, dass ein C ++ – Experte mit einer besseren Antwort kommen wird, aber persönlich mag ich den zweiten Ansatz. Die Verwendung von intelligenten pointersn hilft bei dem Problem, zu vergessen, zu delete und wie Sie sagen, sieht es sauberer aus, als ein Objekt vor der Hand erstellen zu müssen (und es immer noch löschen zu müssen, wenn Sie es auf dem Heap reservieren wollen).