TL; DR: In C ++ ist es immer noch eine gute Idee, einen konstanten Verweis zu übergeben. Keine vorzeitige Optimierung.
TL; DR2: Die meisten Sprichwörter machen keinen Sinn, bis sie es tun.
Ziel
Diese Antwort versucht nur, das verknüpfte Element in den C ++ - Grundrichtlinien zu erweitern (zuerst in amons Kommentar erwähnt) ein wenig zu erweitern.
Diese Antwort versucht nicht, die Frage zu beantworten, wie man die verschiedenen Sprichwörter, die in den Kreisen der Programmierer weit verbreitet waren, richtig denkt und anwendet, insbesondere die Frage der Vereinbarkeit zwischen widersprüchlichen Schlussfolgerungen oder Beweisen.
Anwendbarkeit
Diese Antwort gilt nur für Funktionsaufrufe (nicht entfernbare verschachtelte Bereiche im selben Thread).
(Randnotiz.) Wenn passierbare Dinge aus dem Geltungsbereich herausfallen können (dh eine Lebensdauer haben, die möglicherweise den äußeren Geltungsbereich überschreitet), wird es wichtiger, die Anforderungen der Anwendung an die Verwaltung der Objektlebensdauer vor allen anderen zu erfüllen. Dies erfordert normalerweise die Verwendung von Referenzen, die auch lebenslang verwaltet werden können, z. B. intelligente Zeiger. Eine Alternative könnte die Verwendung eines Managers sein. Beachten Sie, dass Lambda eine Art abnehmbarer Bereich ist. Lambda-Erfassungen verhalten sich wie Objekterfassungen. Seien Sie daher vorsichtig mit Lambda-Erfassungen. Achten Sie auch darauf, wie das Lambda selbst weitergegeben wird - als Kopie oder als Referenz.
Wann soll der Wert übergeben werden?
Bei skalaren Werten (Standardprimitiven, die in ein Maschinenregister passen und eine Wertesemantik aufweisen), für die keine Kommunikation durch Mutabilität (gemeinsame Referenz) erforderlich ist, übergeben Sie den Wert.
In Situationen, in denen Angerufene das Klonen eines Objekts oder Aggregats erfordern, übergeben Sie den Wert, in denen die Kopie des Angerufenen die Anforderung eines geklonten Objekts erfüllt.
Wann soll man als Referenz übergeben, etc.
Übergeben Sie in allen anderen Situationen Zeiger, Verweise, intelligente Zeiger, Handles (siehe: Handle-Body-Idiom) usw. Wenden Sie das Prinzip der konstanten Korrektheit wie gewohnt an, wenn dieser Hinweis befolgt wird.
Dinge (Aggregate, Objekte, Arrays, Datenstrukturen), die einen ausreichend großen Speicherbedarf haben, sollten aus Leistungsgründen immer so entworfen werden, dass das Pass-by-Reference erleichtert wird. Dieser Hinweis gilt auf jeden Fall, wenn es sich um Hunderte von Bytes oder mehr handelt. Dieser Rat ist grenzwertig, wenn es sich um zehn Bytes handelt.
Ungewöhnliche Paradigmen
Es gibt spezielle Programmierparadigmen, die absichtlich kopierintensiv sind. Beispielsweise String-Verarbeitung, Serialisierung, Netzwerkkommunikation, Isolation, Wrapping von Bibliotheken von Drittanbietern, Kommunikation zwischen Prozessen mit gemeinsamem Speicher usw. In diesen Anwendungsbereichen oder Programmierparadigmen werden Daten von Strukturen zu Strukturen kopiert oder manchmal neu gepackt Byte-Arrays.
Wie sich die Sprachspezifikation auf diese Antwort auswirkt, bevor die Optimierung in Betracht gezogen wird.
Sub-TL; DR Bei der Weitergabe einer Referenz sollte kein Code aufgerufen werden. Das Übergeben von const-reference erfüllt dieses Kriterium. Alle anderen Sprachen erfüllen dieses Kriterium jedoch mühelos.
(Anfängern von C ++ wird empfohlen, diesen Abschnitt vollständig zu überspringen.)
(Der Anfang dieses Abschnitts ist teilweise von der Antwort von gnasher729 inspiriert. Es wird jedoch eine andere Schlussfolgerung gezogen.)
C ++ ermöglicht benutzerdefinierte Kopierkonstruktoren und Zuweisungsoperatoren.
(Dies ist (war) eine mutige Wahl, die sowohl erstaunlich als auch bedauerlich ist (war). Es ist definitiv eine Abweichung von der heute akzeptablen Norm im Sprachdesign.)
Auch wenn der C ++ - Programmierer keine definiert, muss der C ++ - Compiler solche Methoden basierend auf Sprachprinzipien generieren und dann bestimmen, ob zusätzlicher Code ausgeführt werden muss memcpy
. Zum Beispiel enthält a class
/ struct
das astd::vector
Mitglied enthält, einen Kopierkonstruktor und einen Zuweisungsoperator haben, der nicht trivial ist.
In anderen Sprachen wird von Kopierkonstruktoren und dem Klonen von Objekten abgeraten (sofern dies für die Semantik der Anwendung nicht unbedingt erforderlich und / oder sinnvoll ist), da Objekte aufgrund des Sprachentwurfs über eine Referenzsemantik verfügen. Diese Sprachen verfügen in der Regel über einen Speicherbereinigungsmechanismus, der auf der Erreichbarkeit und nicht auf dem Besitz des Bereichs oder der Referenzzählung basiert.
Wenn eine Referenz oder ein Zeiger (einschließlich einer konstanten Referenz) in C ++ (oder C) übergeben wird, wird dem Programmierer versichert, dass kein spezieller Code (benutzerdefinierte oder vom Compiler generierte Funktionen) ausgeführt wird, außer der Weitergabe des Adresswerts (Referenz oder Zeiger). Dies ist eine Klarheit des Verhaltens, mit der sich C ++ - Programmierer wohl fühlen.
Der Hintergrund ist jedoch, dass die C ++ - Sprache unnötig kompliziert ist, sodass diese Klarheit des Verhaltens wie eine Oase (ein überlebbarer Lebensraum) in der Nähe einer nuklearen Fallout-Zone wirkt.
Um mehr Segen (oder Beleidigung) hinzuzufügen, führt C ++ universelle Referenzen (r-Werte) ein, um benutzerdefinierte Verschiebungsoperatoren (Verschiebungskonstruktoren und Verschiebungszuweisungsoperatoren) mit guter Leistung zu ermöglichen. Dies kommt einem äußerst relevanten Anwendungsfall (dem Verschieben (Übertragen) von Objekten von einer Instanz zur anderen) zugute, da weniger kopiert und tief geklont werden muss. In anderen Sprachen ist es jedoch unlogisch, von einer solchen Bewegung von Objekten zu sprechen.
(Off-Topic-Bereich) Ein Bereich, der dem Artikel "Want Speed? Pass by Value!" Gewidmet ist. geschrieben in circa 2009.
Dieser Artikel wurde 2009 verfasst und erläutert die Entwurfsbegründung für r-value in C ++. Dieser Artikel bietet ein gültiges Gegenargument zu meiner Schlussfolgerung im vorherigen Abschnitt. Das Codebeispiel und der Leistungsanspruch des Artikels wurden jedoch schon lange widerlegt.
Sub-TL; DR Das Design der r-Wert-Semantik in C ++ ermöglicht eine überraschend elegante benutzerseitige Semantik auf aSort
Funktion. Dieses elegante ist unmöglich in anderen Sprachen zu modellieren (nachzuahmen).
Eine Sortierfunktion wird auf eine gesamte Datenstruktur angewendet. Wie oben erwähnt, ist es langsam, wenn viel kopiert wird. Als eine (praktisch relevante) Leistungsoptimierung ist eine Sortierfunktion so konzipiert, dass sie in einigen anderen Sprachen als C ++ destruktiv ist. Destruktiv bedeutet, dass die Zieldatenstruktur geändert wird, um das Sortierziel zu erreichen.
In C ++ kann der Benutzer eine der beiden Implementierungen aufrufen: eine destruktive mit besserer Leistung oder eine normale, bei der die Eingabe nicht geändert wird. (Die Vorlage wurde der Kürze halber weggelassen.)
/*caller specifically passes in input argument destructively*/
std::vector<T> my_sort(std::vector<T>&& input)
{
std::vector<T> result(std::move(input)); /* destructive move */
std::sort(result.begin(), result.end()); /* in-place sorting */
return result; /* return-value optimization (RVO) */
}
/*caller specifically passes in read-only argument*/
std::vector<T> my_sort(const std::vector<T>& input)
{
/* reuse destructive implementation by letting it work on a clone. */
/* Several things involved; e.g. expiring temporaries as r-value */
/* return-value optimization, etc. */
return my_sort(std::vector<T>(input));
}
/*caller can select which to call, by selecting r-value*/
std::vector<T> v1 = {...};
std::vector<T> v2 = my_sort(v1); /*non-destructive*/
std::vector<T> v3 = my_sort(std::move(v1)); /*v1 is gutted*/
Abgesehen von der Sortierung ist diese Eleganz auch bei der Implementierung eines destruktiven Medianfindungsalgorithmus in einem Array (anfangs unsortiert) durch rekursive Partitionierung nützlich.
Beachten Sie jedoch, dass die meisten Sprachen beim Sortieren einen ausgewogenen Ansatz für binäre Suchbäume anwenden würden, anstatt bei Arrays einen destruktiven Sortieralgorithmus anzuwenden. Daher ist die praktische Relevanz dieser Technik nicht so hoch, wie es scheint.
Wie sich die Compiler-Optimierung auf diese Antwort auswirkt
Wenn Inlining (und auch die Ganzprogrammoptimierung / Verbindungszeitoptimierung) auf mehreren Ebenen von Funktionsaufrufen angewendet wird, kann der Compiler den Datenfluss (manchmal erschöpfend) anzeigen. In diesem Fall kann der Compiler viele Optimierungen anwenden, von denen einige die Erstellung ganzer Objekte im Speicher verhindern. In der Regel spielt es in dieser Situation keine Rolle, ob die Parameter als Wert oder als Konstantenreferenz übergeben werden, da der Compiler eine umfassende Analyse durchführen kann.
Wenn die Funktion der unteren Ebene jedoch etwas aufruft, das sich nicht analysieren lässt (z. B. etwas in einer anderen Bibliothek außerhalb der Kompilierung oder ein Aufrufdiagramm, das einfach zu kompliziert ist), muss der Compiler defensiv optimieren.
Objekte, die größer als ein Maschinenregisterwert sind, können durch explizite Anweisungen zum Laden / Speichern des Speichers oder durch einen Aufruf der ehrwürdigen memcpy
Funktion kopiert werden . Auf einigen Plattformen generiert der Compiler SIMD-Anweisungen, um zwischen zwei Speicherorten zu wechseln, wobei jede Anweisung mehrere zehn Bytes (16 oder 32) bewegt.
Diskussion zum Thema Ausführlichkeit oder visuelle Unordnung
C ++ - Programmierer sind daran gewöhnt, dh solange ein Programmierer C ++ nicht hasst, ist der Aufwand für das Schreiben oder Lesen von const-Referenzen im Quellcode nicht schrecklich.
Möglicherweise wurden die Kosten-Nutzen-Analysen bereits mehrfach durchgeführt. Ich weiß nicht, ob es wissenschaftliche gibt, die zitiert werden sollten. Ich denke, die meisten Analysen wären nicht wissenschaftlich oder nicht reproduzierbar.
Das stelle ich mir vor (ohne Beweise oder glaubwürdige Referenzen) ...
- Ja, dies wirkt sich auf die Leistung der in dieser Sprache geschriebenen Software aus.
- Wenn Compiler den Zweck von Code verstehen, ist er möglicherweise intelligent genug, um dies zu automatisieren
- Leider würde der Compiler in Sprachen, die die Wandlungsfähigkeit (im Gegensatz zur funktionalen Reinheit) bevorzugen, die meisten Dinge als mutiert einstufen, daher würde die automatische Ableitung der Konstanz die meisten Dinge als nicht-konstant ablehnen
- Der mentale Aufwand hängt von den Menschen ab; Leute, die dies für einen hohen mentalen Aufwand halten, hätten C ++ als brauchbare Programmiersprache abgelehnt.