C ++: Ist der Rückgabewert ein L-Wert?

73

Betrachten Sie diesen Code:

struct foo
{
  int a;
};

foo q() { foo f; f.a =4; return f;}

int main()
{
  foo i;
  i.a = 5;
  q() = i;
}

Kein Compiler beschwert sich darüber, auch Clang. Warum ist die q() = ...Zeile korrekt?

John
quelle
6
Wow, gute Frage, +1.
Seth Carnegie
5
Was ist Ihrer Meinung nach mit diesem Code falsch? q()Gibt eine Struktur zurück und weist ihr dann einen Wert zu. Was stimmt damit nicht?
Andy Johnson
1
@Andy: Ich denke, es ist sehr fehleranfällig, da das Zuweisen von Wert zu Rückgabewert normalerweise nichts bewirkt (außer wenn operator = etwas Magie ausführt, was wahrscheinlich eine schlechte Entwurfspraxis ist). Ich habe erwartet, dass dies eine Warnung ist, zum Beispiel, dass keine lokale Variable verwendet wird.
John
1
@Andy Johnson: Das weit verbreitete Missverständnis ist, dass die Leute davon ausgehen, dass alles, was Sie auf der linken Seite der Aufgabe verwenden können, ein Wert sein muss. In diesem Fall scheint diese Anforderung verletzt zu sein. In Wirklichkeit gibt es jedoch keine solche Anforderung. Die integrierte Zuweisung erfordert zwar einen Wert für die LHS, die überlastete Zuweisung jedoch nicht. In diesem Fall haben wir es mit dem überlasteten zu tun, obwohl es nicht offensichtlich ist.
AnT

Antworten:

69

Nein, der Rückgabewert einer Funktion ist genau dann ein l-Wert, wenn es sich um eine Referenz handelt (C ++ 03). (5.2.2 [Ausdruck] / 10)

Wenn der zurückgegebene Typ ein Basistyp wäre, wäre dies ein Kompilierungsfehler. (5,17 [Ausdruck] / 1)

Der Grund dafür ist, dass Sie Elementfunktionen (auch Nicht-Elementfunktionen const) für r-Werte des Klassentyps aufrufen dürfen und die Zuweisung von fooeine implementierungsdefinierte Elementfunktion ist : foo& foo::operator=(const foo&). Die Einschränkungen für Operatoren in Abschnitt 5 gelten nur für integrierte Operatoren (5 [Ausdruck] / 3). Wenn die Überlastauflösung einen überladenen Funktionsaufruf für einen Operator auswählt, gelten stattdessen die Einschränkungen für diesen Funktionsaufruf.

Aus diesem Grund wird manchmal empfohlen, Objekte vom Klassentyp als constObjekte zurückzugeben (z. B. const foo q();). Dies kann sich jedoch negativ auf C ++ 0x auswirken, da die Verschiebungssemantik möglicherweise nicht ordnungsgemäß funktioniert.

CB Bailey
quelle
18
Betrachten Sie als konkretes Beispiel std::cout << "Hello, world" << std::endl. Der erste operator<< ()gibt einen Wert zurück, für den Sie den zweiten aufrufen können operator<< (). Daher ist diese Praxis, Methoden für Rückgabewerte aufzurufen, häufiger als viele Leute denken.
MSalters
4
Dies erinnert mich an ein Zitat aus The Design and Evolution of C++: Dies ist der Ursprung der Vorstellung, dass im Laufe der Jahre eine Faustregel für das Design von C ++ wurde: Benutzerdefinierte und integrierte Typen sollten sich im Verhältnis zu den Sprachregeln gleich verhalten und empfangen das gleiche Maß an Unterstützung durch die Sprache und die dazugehörigen Tools. Als das Ideal formuliert wurde, erhielten integrierte Typen bei weitem die beste Unterstützung, aber C ++ hat dieses Ziel überschritten, sodass integrierte Typen jetzt im Vergleich zu benutzerdefinierten Typen eine etwas schlechtere Unterstützung erhalten.
Seth Carnegie
11
@MSalters ja, aber im Fall gibt coutes tatsächlich einen l-Wert zurück (es gibt sich selbst und als Referenz zurück *this), nicht etwas nach Wert. Es ist also ein bisschen anders.
Seth Carnegie
5
@MSalters: ostream :: operator <<, wie operator = beide geben Referenz zurück, was L-Wert ist.
John
4
@MSalters In std::cout << "duh" << std::endlden operator<<alle Rückkehr nicht konstanten Referenzen (die L - Werte) sind, und weil die Betreiber all nicht konstante Referenzen als erste Parameter erfordern, das Original std::ostreamkann Objekt nicht ein R - Wert sein. In den anderen Beispielen (und ich nehme an, dass dies die Motivation der Frage ist) werden nicht konstante Funktionen vorübergehend aufgerufen.
James Kanze
8

Da Strukturen zugewiesen werden können und Sie q()eine Kopie davon zurückgeben struct foo, wird die zurückgegebene Struktur dem angegebenen Wert zugewiesen .

Dies macht in diesem Fall eigentlich nichts, weil die Struktur danach nicht mehr in den Geltungsbereich fällt und Sie überhaupt keinen Verweis darauf behalten, sodass Sie sowieso nichts damit anfangen können (in diesem speziellen Code).

Dies ist sinnvoller (obwohl immer noch keine "Best Practice")

struct foo
{
  int a;
};

foo* q() { foo *f = new malloc(sizeof(foo)); f->a = 4; return f; }

int main()
{
  foo i;
  i.a = 5;

  //sets the contents of the newly created foo
  //to the contents of your i variable
  (*(q())) = i;
}
Tschad
quelle
5
Was ist der Unterschied zwischen dem und dem: int blah() { int f = 4; return f; } int main() { int a = 99; blah() = a; }Ist es etwas Magisches an Strukturen? Weil dieser Code nicht kompiliert wird.
Seth Carnegie
@ Seth Siehe Charles Baileys Antwort
Chad
2
Ints haben keine Mitgliedsfunktionen, also auch keine int::operator=(int). Das ist das "magische" an Strukturen: Sie haben Standardmethoden.
MSalters
@Seth, da die Funktion in Ihrem Beispiel ein int und kein Objekt zurückgibt, können Sie ihm also nichts zuweisen. Wenn die Funktion einen Verweis auf ein int (int &) zurückgibt, können Sie ihm zuweisen.
Bruno
1
@bruno:s/nothing/anything/
Leichtigkeitsrennen im Orbit
7

Eine interessante Anwendung davon:

void f(const std::string& x);
std::string g() { return "<tag>"; }

...

f(g() += "</tag>");

Hier wird g() +=das temporäre Element geändert, was möglicherweise schneller ist als das Erstellen eines zusätzlichen temporären Datenträgers, +da der für den Rückgabewert von g () zugewiesene Heap möglicherweise bereits über genügend freie Kapazität verfügt </tag>.

Sehen Sie es auf ideone.com mit GCC / C ++ 11 .

Welcher Computeranfänger sagte etwas über Optimierungen und das Böse ...? ; -].

Tony Delroy
quelle