Frage

Referenzen in C ++ verblüfft mich. :)

Die Grundidee ist, dass ich versuche, ein Objekt aus einer Funktion zurückzugeben. Ich würde es gerne tun, ohne einen Zeiger zurückzugeben (weil ich dann manuell manuell müsste delete IT) und ohne den Kopierkonstruktor zu nennen, wenn möglich (für Effizienz natürlich hinzugefügt: Und auch, weil ich mich frage, ob ich es nicht vermeiden kann, einen Kopierkonstruktor zu schreiben).

Insgesamt sind hier die Möglichkeiten, dies zu tun, die ich gefunden habe:

  • Der Funktionsrückgabeart kann entweder die Klasse selbst sein (MyClass fun() { ... }) oder ein Hinweis auf die Klasse (MyClass& fun() { ... }).
  • Die Funktion kann entweder die Variable an der Rückkehrlinie konstruieren (return MyClass(a,b,c);) oder eine vorhandene Variable zurückgeben (MyClass x(a,b,c); return x;).
  • Der Code, der die Variable empfängt, kann auch eine Variable von beiden Typen haben: ((MyClass x = fun(); oder MyClass& x = fun();)
  • Der Code, der die Variable empfängt, kann entweder eine neue Variable im laufenden Fliegen erstellen (MyClass x = fun();) oder zuweisen einer vorhandenen Variablen (MyClass x; x = fun();)

Und einige Gedanken dazu:

  • Es scheint eine schlechte Idee zu sein, den Rückgabetyp zu haben MyClass& Denn dies führt immer dazu, dass die Variable zerstört wird, bevor sie zurückgegeben wird.
  • Der Kopierkonstruktor scheint sich nur zu engagieren, wenn ich eine vorhandene Variable zurücksende. Bei der Rückgabe einer Variablen, die in der Rückkehrlinie konstruiert ist, wird sie nie aufgerufen.
  • Wenn ich das Ergebnis einer vorhandenen Variablen zuordne, tritt der Destruktor immer immer ein, bevor der Wert zurückgegeben wird. Außerdem wird kein Kopierkonstruktor aufgerufen, aber die Zielvariable empfängt die Mitgliederwerte des von der Funktion zurückgegebenen Objekts.

Diese Ergebnisse sind so inkonsistent, dass ich mich völlig verwirrt fühle. Also, was genau passiert hier? Wie soll ich ein Objekt richtig konstruieren und aus einer Funktion zurückgeben?

War es hilfreich?

Lösung

Der beste Weg, um das Kopieren in C ++ zu verstehen, besteht häufig nicht darin, ein künstliches Beispiel zu produzieren und es zu instrumentieren. Der Compiler darf Kopiekonstruktor -Anrufe sowohl entfernen als auch hinzufügen, mehr oder weniger, wenn es ansieht.

Fazit - Wenn Sie einen Wert zurückgeben, einen Wert zurückgeben und sich keine "Ausgaben" machen.

Andere Tipps

Literatur-Empfehlungen: Effektives C ++ von Scott Meyers. Sie finden dort eine sehr gute Erklärung zu diesem Thema (und vielem mehr).

Kurz gesagt, wenn Sie nach Wert zurückgeben, werden der Kopierkonstruktor und der Destruktor standardmäßig beteiligt (es sei denn, der Compiler optimiert sie weg - das passiert in einigen Ihrer Fälle).

Wenn Sie durch Referenz (oder Zeiger) eine Variable zurückgeben, die lokal ist (auf dem Stapel konstruiert), laden Sie Ärger ein, weil das Objekt bei der Rückkehr zerstört wird, sodass Sie dadurch eine baumelnde Referenz haben.

Die kanonische Methode, um ein Objekt in einer Funktion zu konstruieren und es zurückzugeben, ist es nach Wert, wie:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

Wenn Sie dies verwenden, müssen Sie sich keine Sorgen um Eigentumsprobleme, baumelnde Referenzen usw. machen, und der Compiler wird höchstwahrscheinlich den zusätzlichen Kopierkonstruktor / Destruktor -Forderungen für Sie optimieren, sodass Sie sich auch keine Sorgen um die Leistung machen müssen.

Es ist möglich, durch Referenz ein Objekt zurückzugeben, das von konstruiert wurde new (dh auf dem Haufen) - Dieses Objekt wird bei der Rückkehr von der Funktion nicht zerstört. Sie müssen es jedoch später irgendwo später irgendwo durchrufen delete.

Es ist auch technisch möglich, ein Objekt zu speichern, das in einer Referenz nach Wert zurückgegeben wird, z. B.:

MyClass& x = fun();

Afaik ist jedoch nicht viel Sinn, um dies zu tun. Vor allem, weil man diesen Hinweis auf andere Teile des Programms, die außerhalb des aktuellen Bereichs liegen, leicht weitergeben kann; Das Objekt, auf das jedoch verwiesen wird x ist ein lokales Objekt, das zerstört wird, sobald Sie den aktuellen Bereich verlassen. Dieser Stil kann also zu bösen Fehler führen.

lesen über Rvo und NRVO (in einem Wort diese beiden stehen für die Rückkehrwertoptimierung und namens RVO und sind Optimierungstechniken, die vom Compiler verwendet werden, um das zu tun, was Sie erreichen möchten).

Hier auf Stackoverflow finden Sie viele Themen

Wenn Sie ein solches Objekt erstellen:

MyClass foo(a, b, c);

Dann befindet es sich auf dem Stapel im Rahmen der Funktion. Wenn diese Funktion endet, wird der Rahmen vom Stapel gestoßen und alle Objekte in diesem Rahmen werden zerstört. Es gibt keine Möglichkeit, dies zu vermeiden.

Wenn Sie also ein Objekt an einen Anrufer zurückgeben möchten, sind Sie nur Optionen:

  • Rückgabe nach Wert - Ein Kopierkonstruktor ist erforderlich (der Anruf an den Kopierkonstruktor kann jedoch optimiert werden).
  • Geben Sie einen Zeiger zurück und stellen Sie sicher, dass Sie entweder intelligente Zeiger damit umgehen, um ihn zu erledigen, oder löschen Sie ihn selbst sorgfältig, wenn Sie damit fertig sind.

Der Versuch, ein lokales Objekt zu konstruieren und dann einen Verweis auf diesen lokalen Speicher in einen aufrufenden Kontext zurückzugeben, ist nicht kohärent. Ein aufrufender Bereich kann nicht auf Speicher zugreifen, der für den genannten Zielfernrohr vorliegt. Dieser lokale Speicher gilt nur für die Dauer der Funktion, der sie gehört - oder auf andere Weise, während die Ausführung in diesem Bereich bleibt. Sie müssen dies verstehen, um in C ++ zu programmieren.

Über das einzige Mal ist es sinnvoll, eine Referenz zurückzugeben, wenn Sie einen Verweis auf ein bereits bestehendes Objekt zurückgeben. Für ein offensichtliches Beispiel gibt fast jede iOstream -Mitgliedsfunktion einen Verweis auf den iOstream zurück. Der iOstream selbst existiert, bevor eines der Mitgliedsfunktionen aufgerufen wird, und existiert weiterhin, nachdem sie angerufen werden.

Der Standard ermöglicht "Elision kopieren", was bedeutet, dass der Kopierkonstruktor nicht aufgerufen werden muss, wenn Sie ein Objekt zurückgeben. Dies kommt in zwei Formen: Name der Rückgabewertoptimierung (NRVO) und anonymer Rückgabewertoptimierung (normalerweise nur RVO).

Nach dem, was Sie sagen, implementiert Ihr Compiler RVO, aber nicht NRVO - was bedeutet, dass es ist wahrscheinlich Ein etwas älterer Compiler. Die meisten aktuellen Compiler implementieren beide. Der nicht übereinstimmende DTOR in diesem Fall bedeutet, dass es wahrscheinlich so etwas wie GCC 3.4 oder so an der Stelle ist-obwohl ich mich nicht an die Version erinnere, gab es einen einen, der einen Fehler wie diesen hatte. Natürlich ist es auch möglich, dass Ihre Instrumentierung nicht ganz richtig ist, also wird ein CTOR, das Sie nicht instrumentiert haben, und ein passender DTOR wird für dieses Objekt aufgerufen.

Am Ende stecken Sie jedoch mit einer einfachen Tatsache fest: Wenn Sie ein Objekt zurückgeben müssen, müssen Sie ein Objekt zurückgeben. Insbesondere kann eine Referenz nur Zugriff auf eine (möglicherweise modifizierte Version von) einem vorhandenen Objekt erweisen - aber dieses Objekt musste auch irgendwann konstruiert werden. Wenn Sie ein vorhandenes Objekt ändern können, ohne ein Problem zu verursachen, ist das in Ordnung und gut, tun Sie es. Wenn Sie ein neues Objekt anders und getrennt von denjenigen benötigen, die Sie bereits haben, tun Sie dies-das Objekt vorzubereiten und in einem Verweis darauf zu bestehen, kann die Rückkehr selbst schneller machen, sparen jedoch insgesamt keine Zeit. Das Erstellen des Objekts hat ungefähr die gleichen Kosten, die innerhalb oder außerhalb der Funktion ausgeführt werden. Jeder einigermaßen moderne Compiler enthält RVO, sodass Sie keine zusätzlichen Kosten für das Erstellen in der Funktion zahlen und dann zurückgeben - der Compiler automatisiert nur die Zuweisung von Platz für das Objekt, in dem er zurückgegeben werden soll, und die Funktion haben Konstruieren Sie es "an Ort und Stelle", wo es nach Rückkehr der Funktion immer noch zugänglich ist.

Grundsätzlich ist die Rückgabe einer Referenz nur sinnvoll, wenn das Objekt nach dem Verlassen der Methode noch existiert. Der Compiler warnt Sie, wenn Sie einen Verweis auf etwas zurückgeben, das zerstört wird.

Die Rückgabe einer Referenz und nicht eines Objekts nach Wert speichert das Kopieren des Objekts, das möglicherweise von Bedeutung ist.

Referenzen sind sicherer als Zeiger, weil sie unterschiedliche Symantiker haben, aber hinter den Kulissen sind sie Zeiger.

Eine potenzielle Lösung, abhängig von Ihrem Anwendungsfall, besteht darin, das Objekt außerhalb der Funktion standardmäßig zu konstruieren, nehmen in Ein Verweis darauf und initialisieren Sie das referenzierte Objekt in der Funktion wie SO:

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

Dies funktioniert natürlich nicht, wenn es nicht möglich ist (oder ist nicht sinnvoll), a standardmäßig zu konstruieren a Foo Objekt und dann später initialisieren. Wenn dies der Fall ist, besteht Ihre einzige reale Option, um eine Kopienkonstruktion zu vermeiden, darin, einen Zeiger an ein haufenalloziertes Objekt zurückzugeben.

Aber denken Sie dann darüber nach, warum Sie versuchen, den Kopienkonstruktion überhaupt zu vermeiden. Beeinträchtigt die "Ausgaben" für die Kopierkonstruktion Ihr Programm wirklich oder ist dies ein Fall einer vorzeitigen Optimierung?

Sie stecken mit beiden fest:

1) Rückgabe eines Zeigers

Myclass* func () {// einige stuf return neue myclass (a, b, c); }

2) Rückgabe einer Kopie des Objekts MyClass func () {return myclass (a, b, c); }

Die Rückgabe einer Referenz ist nicht gültig, da das Objekt nach dem Verlassen des Func -Bereichs zerstört werden soll, außer wenn die Funktion ein Mitglied der Klasse ist und die Referenz aus einer Variablen der Klasse stammt.

Keine direkte Antwort, aber ein praktikabler Vorschlag: Sie können auch einen Zeiger zurückgeben, der in ein Auto_Ptr oder smart_ptr eingewickelt ist. Dann haben Sie die Kontrolle darüber, was Konstrukteure und Zerstörer aufgerufen werden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top