2014-09-24 8 views
8

Als ich die Antwort auf this question las, war ich überrascht zu sehen, dass std::min(std::initializer_list<T>) seine Argumente nach Wert nimmt.Warum nimmt std :: min (std :: initializer_list <T>) Argumente nach Wert?

Wenn Sie std::initializer_list in der Weise verwenden, die durch seinen Namen impliziert wird, d. H. Als Initialisierer für ein Objekt, verstehe ich, dass wir seine Elemente nicht kopieren, da sie sowieso kopiert werden, um das Objekt zu initialisieren. In diesem Fall benötigen wir hier jedoch höchstwahrscheinlich keine Kopie, so dass es viel vernünftiger erscheint, die Argumente als std::initializer_list<const T&> zu nehmen, wenn es nur möglich wäre.

Was ist die beste Vorgehensweise für diese Situation? Sollten Sie nicht die initializer_list Version von std::min anrufen, wenn Sie nicht unnötige Kopien machen wollen, oder gibt es einen anderen Trick, um die Kopie zu vermeiden?

+0

AFAIK, die Verwendung einer Referenz benötigt mehr CPU-Zyklen als die Übergabe einer 'int's-Kopie und benötigt mehr Speicher (mindestens auf x86) als die Übergabe kleinerer Variablen. – GingerPlusPlus

+0

True, aber std :: min ist nicht auf int (oder einen beliebigen numerischen Typ) beschränkt. Und bei großen Objekten kann es sehr wichtig sein, auf die Kopie zu verzichten. – gTcV

+1

Ich denke, der Grund ist in [N2722] (http://open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2722.pdd) beschrieben, obwohl ich diesen Leistungsvergleich und das Fazit. Es könnte irgendwo mehr Diskussionen geben. – dyp

Antwort

2

Um die Kommentare zusammenzufassen:

Ein std::initializer_list soll ein leichtes Proxy sein, wie auf cppreference.com beschrieben. Daher ist eine Kopie einer Initialisiererliste sollte sehr schnell sein, da die zu Grunde liegenden Elemente, die nicht kopiert werden:

C++ 11 §18.9/2

Ein Objekt des Typs initializer_list<E> Zugriff auf ein Array liefert von Objekten des Typs const E. [Hinweis: Ein Paar Zeiger oder ein Zeiger plus eine Länge wäre offensichtlich Darstellungen für initializer_list. initializer_list wird verwendet, um Initialisierungslisten gemäß 8.5.4 zu implementieren. Beim Kopieren einer Initialisierungsliste werden die zugrunde liegenden Elemente nicht kopiert. - Endnote]

Referenz verwendet werden, obwohl würde einkochen einen Zeiger auf die Verwendung und damit eine zusätzliche Indirektion.

Das eigentliche Problem der std::min daher nicht, dass es die initializer_list von Wert annimmt, sondern vielmehr, dass die Argumente zu initializer_list kopiert werden, wenn sie zur Laufzeit berechnet werden. [1] Dass der Benchmark bei [1] gebrochen wurde wie gefunden later ist bedauerlich.

auto min_var = std::min({1, 2, 3, 4, 5}); // fast 
auto vec = std::vector<int>{1, 2, 3, 4, 5}; 
min_var = std::min({vec[0], vec[1], vec[2], vec[3], vec[4]}); // slow 

[1]: N2722 p. 2

+0

Ich glaube, Sie haben sowohl die Frage als auch die Kommentare völlig falsch verstanden. Sie beschweren sich, dass die Elemente in der "initializer_list" zur Laufzeit kopiert werden, anstatt eine "initializer_list" von Referenzen zu haben. –

+0

Guter Punkt! Ich habe den Link "Zeige 8 weitere Kommentare" übersehen, der zu einem Chaos geführt hat, sobald ich es gesehen habe. Obwohl ich nicht sagen würde, dass ich die Frage missverstanden habe. Da die Initialisierungsliste beim Erstellen keine Kopien erstellen muss.Dies hängt von der Situation ab. – mfuchs

+0

Ihr "Benchmark" scheint zu behaupten, dass das Kopieren eines 'int' langsam ist. Sollte es nicht ein Vektor großer, teuer zu kopierender Objekte sein? (Ok, ich sehe, dass der verlinkte Artikel genau das hat, jeder ist eine 'std :: list' aus Millionen von Elementen) –

5

Es gibt keine std::initializer_list<const T&>. Initialisierungslisten können nur Objekte nach Wert halten.

See [dcl.init.list]/5:

Ein Objekt des Typs std::initializer_list<E> aus einer Initialisiererliste aufgebaut ist, als ob die Umsetzung eine temporäre Anordnung von N Elementen des Typs const E zugeordnet, wobei N die Anzahl der Elemente in der Initialisierungsliste.

Es gibt keine Arrays von Referenzen, daher kann es auch keinen initializer_list von Referenzen geben.

Mein Vorschlag in diesem Fall wäre, einen Ersatz für std::min schreiben, die eine variadic Vorlage von Weiterleitungsreferenzen nimmt. Dies funktioniert (obwohl ich sicher bin, kann es verbessert werden):

template<typename T, typename... Args> 
T vmin(T arg1, Args&&... args) 
{ 
    T *p[] = { &arg1, &args... }; 

    return **std::min_element(begin(p), end(p), 
      [](T *a, T *b) { return *a < *b; }); 
} 

See it working - mit min oder mmin, zwei zusätzliche Kopien gemacht werden (für a und b).

Verwandte Themen