it-swarm.com.de

Ist es in C ++ besser, als Wert oder als konstante Referenz zu übergeben?

Ist es in C++ besser, als Wert oder als konstante Referenz zu übergeben?

Ich frage mich, welche Praxis besser ist. Mir ist klar, dass ein ständiger Verweis die Leistung des Programms verbessern sollte, da Sie keine Kopie der Variablen erstellen.

202
Matt Pascoe

Früher war es allgemein empfohlene Best Practice1 um die Pass-by-Konstante ref für alle Typen zu verwenden, außer für eingebaute Typen (char, int, double usw.), für Iteratoren und für Funktionsobjekte (Lambdas, Klassen, die von std::*_function).

Dies galt insbesondere vor der Existenz von Bewegungssemantik. Der Grund ist einfach: Wenn Sie als Wert übergeben haben, musste eine Kopie des Objekts erstellt werden. Mit Ausnahme von sehr kleinen Objekten ist dies immer teurer als die Übergabe einer Referenz.

Mit C++ 11 haben wir Semantik verschieben gewonnen. Kurz gesagt, die Verschiebungssemantik ermöglicht es, dass ein Objekt in einigen Fällen "nach Wert" übergeben werden kann, ohne es zu kopieren. Dies ist insbesondere dann der Fall, wenn das Objekt, das Sie übergeben, ein rWert ist.

An sich ist das Bewegen eines Objekts immer noch mindestens so teuer wie das Übergeben von Referenzen. In vielen Fällen kopiert eine Funktion ein Objekt jedoch trotzdem intern - d. H. Sie übernimmt Ownership des Arguments.2

In diesen Situationen haben wir den folgenden (vereinfachten) Kompromiss:

  1. Wir können das Objekt als Referenz übergeben und dann intern kopieren.
  2. Wir können das Objekt als Wert übergeben.

"Übergebe den Wert" bewirkt weiterhin, dass das Objekt kopiert wird, es sei denn, es handelt sich bei dem Objekt um einen Wert. Bei einem r-Wert kann das Objekt stattdessen verschoben werden, sodass der zweite Fall plötzlich nicht mehr "Kopieren, dann verschieben", sondern "Verschieben, dann (möglicherweise) erneut verschieben" lautet.

Bei großen Objekten, die geeignete Verschiebungskonstruktoren implementieren (wie Vektoren, Zeichenfolgen usw.), ist der zweite Fall dann immens effizienter als der erste. Es wird daher empfohlen, die Übergabe als Wert zu verwenden , wenn die Funktion das Argument besitzt und der Objekttyp effizientes Verschieben unterstützt .


Eine historische Anmerkung:

Tatsächlich sollte jeder moderne Compiler in der Lage sein, herauszufinden, wann die Übergabe von Werten teuer ist, und den Aufruf implizit zu konvertieren, um, wenn möglich, eine const ref zu verwenden.

In der Theorie. In der Praxis können Compiler dies nicht immer ändern, ohne die Binärschnittstelle der Funktion zu beschädigen. In einigen besonderen Fällen (wenn die Funktion eingebunden ist) wird die Kopie tatsächlich gelöscht, wenn der Compiler herausfindet, dass das ursprüngliche Objekt durch die Aktionen in der Funktion nicht geändert wird.

Im Allgemeinen kann der Compiler dies jedoch nicht feststellen, und das Aufkommen der Verschiebungssemantik in C++ hat diese Optimierung erheblich weniger relevant gemacht.


1 Z.B. in Scott Meyers Effektives C++.

2 Dies gilt besonders häufig für Objektkonstruktoren, die Argumente verwenden und intern speichern, um Teil des Status des konstruierten Objekts zu sein.

192
Konrad Rudolph

Edit: Neuer Artikel von Dave Abrahams auf cpp-next:

Willst du Geschwindigkeit? Übergebe den Wert.


Das Übergeben von Werten für Strukturen, bei denen das Kopieren kostengünstig ist, hat den zusätzlichen Vorteil, dass der Compiler davon ausgehen kann, dass die Objekte keinen Aliasnamen haben (nicht dieselben Objekte sind). Bei Verwendung von Pass-by-Reference kann der Compiler dies nicht immer voraussetzen. Einfaches Beispiel:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

der Compiler kann es optimieren

g.i = 15;
f->i = 2;

da es weiß, dass f und g nicht den gleichen Ort teilen. Wenn g eine Referenz (foo &) wäre, hätte der Compiler das nicht annehmen können. da g.i dann mit f-> i verfälscht werden könnte und einen Wert von 7 haben müsste, müsste der Compiler den neuen Wert von g.i erneut aus dem Speicher holen.

Weitere praktische Regeln finden Sie in Konstruktoren verschieben Artikel (sehr empfehlenswerte Lektüre).

  • Wenn die Funktion beabsichtigt, das Argument als Nebeneffekt zu ändern, nehmen Sie es als Nicht-Konstanten-Referenz.
  • Wenn die Funktion ihr Argument nicht ändert und das Argument vom primitiven Typ ist, nehmen Sie es nach Wert.
  • Nehmen Sie es ansonsten als konstanten Verweis, außer in den folgenden Fällen
    • Wenn die Funktion dann ohnehin eine Kopie der const-Referenz erstellen müsste, nehmen Sie sie als Wert.

"Primitiv" oben bedeutet im Grunde genommen kleine Datentypen, die einige Bytes lang sind und nicht polymorph (Iteratoren, Funktionsobjekte usw.) oder teuer zu kopieren sind. In diesem Papier gibt es eine andere Regel. Die Idee ist, dass man manchmal eine Kopie erstellen möchte (falls das Argument nicht geändert werden kann) und manchmal nicht (falls man das Argument selbst in der Funktion verwenden möchte, falls das Argument ohnehin temporär war , beispielsweise). Wie das geht, wird im Beitrag ausführlich erläutert. In C++ 1x kann diese Technik nativ mit Sprachunterstützung verwendet werden. Bis dahin würde ich mit den obigen Regeln gehen.

Beispiele: Um einen String in Großbuchstaben zu schreiben und die Großbuchstaben-Version zurückzugeben, sollte man immer den Wert übergeben: Man muss sowieso eine Kopie davon erstellen (man kann die const-Referenz nicht direkt ändern) - also besser so transparent wie möglich machen den Anrufer und machen Sie diese Kopie frühzeitig, damit der Anrufer so viel wie möglich optimieren kann - wie in diesem Artikel beschrieben:

my::string uppercase(my::string s) { /* change s and return it */ }

Wenn Sie den Parameter jedoch trotzdem nicht ändern müssen, nehmen Sie Bezug auf const:

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

Wenn Sie jedoch den Zweck haben, etwas in das Argument zu schreiben, übergeben Sie es als Nicht-Konstanten-Referenz

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}

Kommt auf den Typ an. Sie fügen den kleinen Aufwand hinzu, einen Verweis und eine Dereferenzierung vornehmen zu müssen. Bei Typen mit einer Größe, die kleiner oder gleich den Zeigern ist, die den Standardkopiercode verwenden, ist es wahrscheinlich schneller, den Wert zu übergeben.

12
Lou Franco

Wie bereits erwähnt, kommt es auf den Typ an. Bei integrierten Datentypen ist es am besten, den Wert zu übergeben. Sogar einige sehr kleine Strukturen, z. B. ein Paar Ints, können eine bessere Leistung erzielen, wenn sie am Wert vorbeigehen.

Angenommen, Sie haben einen ganzzahligen Wert und möchten ihn an eine andere Routine übergeben. Wenn dieser Wert für die Speicherung in einem Register optimiert wurde und Sie ihn als Referenz übergeben möchten, muss er zuerst im Speicher gespeichert und dann ein Zeiger auf den Speicher auf dem Stapel abgelegt werden, um den Aufruf auszuführen. Wenn es als Wert übergeben wurde, ist nur das auf den Stapel geschobene Register erforderlich. (Die Details sind etwas komplizierter als bei verschiedenen aufrufenden Systemen und CPUs).

Wenn Sie die Template-Programmierung ausführen, müssen Sie in der Regel immer const ref übergeben, da Sie nicht wissen, welche Typen übergeben werden. Die Übergabe von Strafen für die Übergabe schlechter Werte ist viel schlimmer als die Übergabe eines integrierten Typs von const ref.

8
Torlack

Das ist, was ich normalerweise arbeite, wenn ich die Oberfläche einer Nicht-Template-Funktion entwerfe:

  1. Übergeben Sie den Wert, wenn die Funktion den Parameter nicht ändern möchte und der Wert günstig zu kopieren ist (int, double, float, char, bool usw.). Beachten Sie, dass std :: string, std :: vector und der Rest der Container in der Standardbibliothek sind NICHT)

  2. Übergeben Sie den const-Zeiger, wenn das Kopieren des Werts teuer ist und die Funktion den Wert, auf den verwiesen wird, nicht ändern möchte und NULL ein Wert ist, den die Funktion verarbeitet.

  3. Übergeben Sie den Zeiger non-const, wenn das Kopieren des Werts teuer ist und die Funktion den Wert ändern möchte, auf den verwiesen wird, und NULL ein Wert ist, den die Funktion verarbeitet.

  4. Übergeben Sie eine const-Referenz, wenn das Kopieren des Werts teuer ist und die Funktion den Wert, auf den verwiesen wird, nicht ändern möchte und NULL kein gültiger Wert wäre, wenn stattdessen ein Zeiger verwendet würde.

  5. Übergeben Sie eine Nicht-Konstanten-Referenz, wenn das Kopieren des Werts teuer ist und die Funktion den angegebenen Wert ändern möchte und NULL kein gültiger Wert wäre, wenn stattdessen ein Zeiger verwendet würde.

6
Martin G

Klingt so, als hättest du deine Antwort. Wertübergabe ist teuer, gibt Ihnen jedoch eine Kopie, mit der Sie bei Bedarf arbeiten können.

5
GeekyMonkey

In der Regel ist es besser, einen konstanten Verweis zu verwenden. Wenn Sie Ihr Funktionsargument jedoch lokal ändern müssen, sollten Sie besser die Übergabe von Werten verwenden. Für einige Grundtypen ist die Leistung im Allgemeinen die gleiche, sowohl für die Übergabe nach Wert als auch nach Referenz. Tatsächlich wird die Referenz intern durch einen Zeiger dargestellt. Aus diesem Grund können Sie beispielsweise davon ausgehen, dass für einen Zeiger beide Übergaben hinsichtlich der Leistung gleich sind oder dass sogar die Übergabe durch einen Wert aufgrund unnötiger Dereferenzierung schneller sein kann.

4
sergtk

Als Faustregel gilt: Wert für Nichtklassentypen und Konstantenreferenz für Klassen. Wenn eine Klasse wirklich klein ist, ist es wahrscheinlich besser, den Wert zu übergeben, aber der Unterschied ist minimal. Was Sie wirklich vermeiden möchten, ist, eine gigantische Klasse nach Wert zu übergeben und alles duplizieren zu lassen - dies wird einen großen Unterschied machen, wenn Sie beispielsweise einen std :: vector mit einer ganzen Reihe von Elementen übergeben.

1
Peter

Übergeben Sie den Wert für kleine Typen.

Übergeben Sie const-Referenzen für große Typen (die Definition von big kann zwischen Maschinen variieren), ABER in C++ 11 übergeben Sie den Wert, wenn Sie die Daten konsumieren möchten, da Sie die Verschiebungssemantik ausnutzen können. Beispielsweise:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

Jetzt würde der aufrufende Code tun:

Person p(std::string("Albert"));

Und nur ein Objekt würde erstellt und direkt in das Element name_ In der Klasse Person verschoben. Wenn Sie einen const-Verweis übergeben, muss eine Kopie erstellt werden, um sie in name_ Zu platzieren.

1
Germán Diago