Warum wird "std :: initializer_list" oft als Wert übergeben?

71

In fast jedem Beitrag, den ich auf SO sehe und an dem a beteiligt ist std::initializer_list, neigen die Leute dazu, einen std::initializer_listWert zu übergeben. Nach diesem Artikel:

http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

man sollte den Wert übergeben, wenn man eine Kopie des übergebenen Objekts erstellen möchte. Aber das Kopieren von a std::initializer_listist keine gute Idee

Durch das Kopieren von a werden std::initializer_listdie zugrunde liegenden Objekte nicht kopiert. Es ist nicht garantiert, dass das zugrunde liegende Array existiert, nachdem die Lebensdauer des ursprünglichen Initialisierungslistenobjekts abgelaufen ist.

Warum wird eine Instanz davon oft nach Wert übergeben und nicht beispielsweise nach dem const&, was garantiert keine unnötige Kopie ergibt?

user1095108
quelle
1
Soweit ich weiß, identifizieren Compiler die gängige Redewendung "Return by Value" und optimieren sie stattdessen als "Move", bei dessen Implementierung tatsächlich keine Speicherkopie erforderlich ist.
MatthewD
1
@MatthewD Wir reden aber nicht darüber, hierher zurückzukehren.
Konrad Rudolph

Antworten:

61

Es wird als Wert übergeben, weil es billig ist. std::initializer_listDa es sich um einen dünnen Wrapper handelt, wird er höchstwahrscheinlich als Zeigerpaar implementiert, sodass das Kopieren (fast) so billig ist wie das Übergeben als Referenz. Außerdem führen wir keine Kopie durch, sondern (normalerweise) eine Verschiebung, da das Argument in den meisten Fällen ohnehin aus einem temporären Argument aufgebaut ist. Dies hat jedoch keinen Einfluss auf die Leistung - das Verschieben von zwei Zeigern ist genauso teuer wie das Kopieren.

Andererseits kann der Zugriff auf die Elemente einer Kopie schneller sein, da wir eine zusätzliche Dereferenzierung (die der Referenz) vermeiden.

Konrad Rudolph
quelle
1
const&wird jedoch nicht unbedingt als Zeiger implementiert (wenn ich mich richtig an den Standard erinnere), könnte es auch eine Art Alias ​​sein.
user1095108
11
@ user1095108 Richtig, der Standard gibt das nicht an. In der realen Welt gibt es jedoch keinen magischen „Alias“ -Typ. Eine Referenz wird entweder vollständig entfernt - im lokalen Bereich können wir stattdessen nur auf die ursprüngliche Entität verweisen - oder durch einen Zeiger ersetzt. Wenn Sie einen Wert als Referenz auf eine Funktion übergeben (und diese Funktion ist nicht inline), wird praktisch garantiert , dass er durch einen Zeiger ersetzt wird.
Konrad Rudolph
1
@ user1095108 Auch wahr. Die bloßen Kosten für den einmaligen Zugriff über eine Referenz können jedoch die Kosten für das Kopieren eines zusätzlichen Zeigers beim Übergeben des Arguments gut ausgleichen (tatsächlich kommt es wahrscheinlich gleich heraus).
Konrad Rudolph
9
@ user1095108 Es ist eigentlich Art-of garantiert: der Standard auch geht std::initializer_listvon Wert. Das ist ein sehr starker Hinweis. Wenn die Standardbibliothek Pass-by-Value verwendet, können Sie sicher dasselbe tun, denn selbst wenn es ineffizient wäre, würden Sie diesen Effekt trotzdem erleiden. Darüber hinaus gibt der Standard explizite Hinweise zur Implementierung (§ 18.9 / 2: „Ein Zeigerpaar oder ein Zeiger plus eine Länge wären offensichtliche Darstellungen für initializer_list.“).
Konrad Rudolph
2
@ user1095108 Konzeptionell sind initializer_listes zwei Iteratoren. Die gesamte STL basiert auf der Annahme, dass das Kopieren von Iteratoren billig ist. Es gibt viel mehr Argumente für die Übergabe von Werten als die von Konrad vorgebrachten - das Wichtigste ist wahrscheinlich, dass der übergebene Wert ein temporärer Wert mit einem trivialen Destruktor ist und daher direkt konstruiert werden kann, da das Argument wahrscheinlich der ist am aussagekräftigsten --- aber Überlegungen zur Leistung können nicht gegen die Übergabe von Werten geltend gemacht werden.
James Kanze
14

Wahrscheinlich aus den gleichen Gründen werden Iteratoren fast immer als Wert übergeben: Das Kopieren eines Iterators wird als "billig" angesehen. Im Fall von initializer_listgibt es auch die Tatsache, dass die meisten Instanzen temporär mit trivialen Destruktoren sind, so dass der Compiler sie direkt dort erstellen kann, wo er das Funktionsargument ohne Kopie platziert. Schließlich gibt es die Tatsache, dass die aufgerufene Funktion wie Iteratoren wahrscheinlich den Wert ändern möchte, was bedeutet, dass sie ihn lokal kopieren müsste, wenn sie von einem Verweis auf const übergeben würde.

BEARBEITEN:

Nur zur Verallgemeinerung: Die Standardbibliothek geht allgemein davon aus, dass es besser ist, Iteratoren, initializer_lists und funktionale Objekte nach Wert zu übergeben. Daher sollten Sie sicherstellen, dass alle von Ihnen entworfenen Iteratoren, Iteratorlisten oder Funktionsobjekte billig zu kopieren sind, und Sie sollten in Ihrem eigenen Code davon ausgehen, dass sie billig zu kopieren sind. Die traditionelle Regel, wann Verweise auf const und wann Wert verwendet werden sollen, sollte wahrscheinlich geändert werden, um dies widerzuspiegeln:

Verwenden Sie die Referenzübergabe an const für andere Klassentypen als Iteratoren, initializer_lists oder Funktionsobjekte. Verwenden Sie andernfalls den Wert pass by.

James Kanze
quelle
1
Aber warum zum std::function<>Beispiel eine Instanz kopieren ? Sicherlich ist es nicht billig zu kopieren.
user1095108
1
@ user1095108 Die Standardbibliothek übergibt sie immer als Kopie. Eine Implementierung sollte also alles tun, damit sie billig zu kopieren ist. (Und Sie sollten es vermeiden, Dinge zu tun, die das Kopieren teuer machen würden: Ein std::functionmit einem großen std::vectorInneren ist keine gute Idee.)
James Kanze
Apropos std::function: Das Ergebnis von std::bindkann voneinander verschoben werden, und durch Verschieben werden gebundene Argumente verschoben. Ich habe debuggt, um zu überprüfen, ob dies der Fall ist. Daher sollte es relativ effizient sein, sich von std::function's zu entfernen , die beispielsweise gebundene Vektoren enthalten - da Vektoren effizient verschoben werden können.
Haelix
@haelix Move-Semantik führt eine weitere Reihe von zu berücksichtigenden Problemen ein, aber ich denke nicht, dass sie die allgemeine Regel wesentlich ändern. Initialisierungslisten werden wahrscheinlich immer in Kontexten verwendet, in denen eine Verschiebung relevant ist. Iteratoren oft nicht. Funktionsobjekte variieren je nach Objekttyp. Ich würde immer noch versuchen, alle drei billig zu kopieren; Der unterstützende Schritt hängt davon ab, wie billig es mir gelingt, eine Kopie zu erstellen.
James Kanze
@ user1095108 Auch wenn wir akzeptiert haben, dass einige std::functions nicht billig zu kopieren sind, sind andere Arten von Funktoren normalerweise ziemlich kostenlos.
Leichtigkeitsrennen im Orbit