2010-04-11 4 views
19

Ich bin jetzt wirklich verwirrt darüber, wie und welche Methode verwendet wird, um Objekt von einer Funktion zurückzugeben. Ich möchte Feedback zu den Lösungen für die gegebenen Anforderungen bekommen.Zurückkehrendes Objekt von der Funktion

Szenario A: Das zurückgegebene Objekt soll in einer Variablen gespeichert werden, die während ihrer Lebensdauer nicht geändert werden muss. Somit

const Foo SomeClass::GetFoo() { 
return Foo(); 
} 

als aufgerufen:

someMethod() { 
const Foo& l_Foo = someClassPInstance->GetFoo(); 
//... 
} 

Scneraio B: Das zurückgegebene Objekt wird in einer Variablen gespeichert werden, die während der Laufzeit geändert werden. So

void SomeClass::GetFoo(Foo& a_Foo_ref) { 
    a_Foo_ref = Foo(); 
    } 

als aufgerufen:

someMethod() { 
Foo l_Foo; 
someClassPInstance->GetFoo(l_Foo); 
//... 
} 

ich eine Frage habe hier: Lassen Sie uns sagen, dass Foo nicht über einen Standardkonstruktor verfügen kann. Dann, wie würden Sie, dass in dieser Situation umgehen, da wir dies nicht mehr schreiben kann nicht:

Foo l_Foo 

Szenario C:

Foo SomeClass::GetFoo() { 
return Foo(); 
} 

aufgerufen, wie:

someMethod() { 
Foo l_Foo = someClassPInstance->GetFoo(); 
//... 
} 

Ich denke, Dies ist nicht der empfohlene Ansatz, da hierfür zusätzliche Provisorien erstellt werden müssen.

Was denkst du? Empfiehlst du dir stattdessen einen besseren Weg dies zu handhaben?

+2

http://en.wikipedia.org/wiki/Return_value_optimization – vladr

+0

Szenario A ist in Ordnung, aber wirklich keine Verbesserung gegenüber C.Ich würde C verwenden, es sei denn Foo's Standardkonstruktor ist "schnell", Foo's Kopie ist "langsam" und Sie wollen nicht darauf vertrauen, dass der Compiler schlau genug ist, um unnötige Kopien zu vermeiden, in welchem ​​Fall B auch akzeptabel ist. – sellibitze

+0

Beachten Sie, dass 'a_Foo_ref = Foo();' Objekt erstellt und die Kopie ausführt. Es entspricht fast dem letzten Szenario – Anycorn

Antwort

16

Lassen Sie uns zunächst in die Dinge suchen, die hier ins Spiel kommen:

(a) Ausweitung der Lebensdauer eines temporären, wenn es einen Verweis auf initialisieren verwendet wird - ich davon erfuhr in this publication von Andrei Anexandrescu. Auch hier fühlt es sich seltsam, aber nützlich:

class Foo { ... } 

Foo GetFoo() { return Foo(); } // returning temporary 

void UseGetFoo() 
{ 
    Foo const & foo = GetFoo(); 
    // ... rock'n'roll ... 
    foo.StillHere(); 
} 

Die Regel besagt, dass, wenn eine Referenz mit einem temporären initialisiert wird, wird die temporäre Lebenszeit verlängert, bis die Referenz den Gültigkeitsbereich verlässt. (this reply zitiert der Kanon)

(b) Rückgabewert Optimierung - (wikipedia) - die beiden Kopien lokal -> Rückgabewert -> lokale kann unter Umständen verzichtet werden. Das ist eine überraschende Regel, da der Compiler das beobachtbare Verhalten ändern kann, aber nützlich ist.

Da haben Sie es. C++ - seltsam, aber nützlich.


So in Ihren Szenarien suchen

Szenario A: Sie einen temporären zurückkehren, und binden Sie es an eine Referenz - die Lebensdauer des vorübergehend auf die Lebensdauer von l_Foo verlängert wird.

Beachten Sie, dass dies nicht funktionieren würde, wenn GetFoo einen Verweis anstelle eines temporären zurückgeben würde.

Szenario B: Works, mit der Ausnahme, dass es Kräfte ein Construct-Construct-Copy-Zyklus (das als einziges Konstrukt viel teurer sein kann), und das Problem, das Sie erwähnen, über einen Standardkonstruktor erforderlich ist.

Ich würde dieses Muster nicht verwenden, um ein Objekt zu erstellen - nur um ein existierendes zu verändern.

Szenario C: Die Kopien von Provisorien können vom Compiler weggelassen werden (ab RVO-Regel). Es gibt leider keine Garantie - aber moderne Compiler implementieren RVO.

Rvalue references in C++ 0x ermöglicht es Foo, einen Ressource-Pilfering-Konstruktor zu implementieren, der nicht nur die Unterdrückung der Kopien garantiert, sondern auch in anderen Szenarien nützlich ist.

(Ich bezweifle, dass es ein Compiler ist, die R-Wert Referenzen implementiert, aber nicht RVO. Es gibt jedoch Situationen, in denen RVO nicht kicken können.)


Eine Frage wie diese erfordert erwähnen intelligente Zeiger, wie shared_ptr und unique_ptr (letzteres ist ein "sicherer" auto_ptr). Sie sind auch in C++ 0x. Sie bieten ein alternatives Muster für Funktionen zum Erstellen von Objekten.


+0

@peter: Danke für diese ausführliche Antwort mit diesen Links! Ich schätze es sehr. Ich habe versucht, dies für eine lange Zeit herauszufinden, daher bin ich mit Szenario A vertraut. Ich hatte eine Frage zu Szenario B. Normalerweise verwende ich B, wenn ich ein Objekt modifizieren muss und nicht, wenn ich es innerhalb von GetFoo erstellen muss (). Nun, vorausgesetzt, dass; wenn wir 'Foo l_Foo anrufen; someClassPInstance-> GetFoo (l_Foo); ':: Wir senden die Referenz von l_Foo, also hier keine Provisorien. Und dann, in 'void SomeClass :: GetFoo (Foo & a_Foo_ref) {// ..}' lässt sich einfach sagen, ich modifiziere es und erstelle es nicht. Glaubst du dann ist B eine gute Option? – brainydexter

+0

Schau dir auch meinen letzten Kommentar zur Originalfrage an. – brainydexter

+0

@brainydexter: Ja, das ist ein typisches Anwendungsszenario für eine Referenz. Es bedarf einiger Dokumentation (out oder in/out etc.). Danke für die Referenz, ich werde mit einem Link aktualisieren. --- Ich habe kürzlich mit rvalue Referenzen herumgespielt - es war auch eine gute Übung für mich. – peterchen

4

Von den drei Szenarien ist Nummer 3 die ideale Methode, die Sie wahrscheinlich verwenden sollten. Sie werden nicht für zusätzliche Kopien bezahlen, da der Compiler copy elision verwenden kann, um die Kopie zu vermeiden, wenn möglich.

Secnario A ist falsch. Sie erhalten einen Verweis auf ein temporäres Objekt, das zerstört wird, wenn die Anweisung mit dem Funktionsaufruf beendet wird. Okay, ist Szenario A nicht falsch, aber Sie sollten noch Szenario C.

Secnario B funktioniert gut verwenden, aber:

Lassen Sie uns sagen, dass Foo nicht über einen Standardkonstruktor verfügen kann. Wie würdest du in dieser Situation damit umgehen, da wir das nicht mehr schreiben können: Foo l_Foo.

Foo muss einen Standardkonstruktor haben. Auch wenn Sie es nicht geben, muss der Compiler für Sie. Wenn Sie einen Konstruktor private deklarieren, können Sie diese Aufrufmethode nicht verwenden. Sie müssen entweder Foo default konstruierbar machen oder Secnario C verwenden.

+1

'const Foo SomeClass :: GetFoo() ':: Szenario A :: Ich gebe den Verweis auf ein temporäres hier nicht zurück. – brainydexter

+0

@brainydexter: Ups .. Sie sind richtig. Ich glaube jedoch, dass es immer noch falsch ist. Ich bin mir nicht 100% ig sicher, aber ich glaube, dass das Vorübergehende zerstört werden wird, obwohl wir einen Hinweis darauf haben. Sie müssen das tatsächliche Objekt herum haben. –

+1

Szenario A ist in Ordnung, aber wirklich keine Verbesserung gegenüber C. Es gibt eine spezielle Regel in C++, die in diesem Fall die Lebensdauer des temporären Objekts verlängert. – sellibitze

Verwandte Themen