2010-09-13 12 views
10

Mögliche Duplizieren:
how to “return an object” in C++C++ Vektor, Rückkehr gegen Parameter

Ich frage mich, ob es einen Unterschied zwischen den drei folgenden Ansätze ist:

void FillVector_1(vector<int>& v) { 
    v.push_back(1); // lots of push_backs! 
} 

vector<int> FillVector_2() { 
    vector<int> v; 
    v.push_back(1); // lots of push_backs! 
    return v; 
} 

vector<int> FillVector_3() { 
    int tab[SZ] = { 1, 2, 3, /*...*/ }; 
    return vector<int>(tab, tab + SZ); 
} 
+1

in Leistung? – Anycorn

+4

Gehen Sie einfach nach Wert zurück und sorgen Sie sich um die Leistung, wenn Sie das Gefühl haben, dass es tatsächlich ein Problem ist. Dann profiliere es, damit du nicht raten musst. Ich schließe präventiv als [doppelt] (http://stackoverflow.com/questions/3350385/how-to-return-an-object-in-c). – GManNickG

+1

Ich hätte nicht gewählt, um dies als ein Duplikat aus mehreren Gründen zu schließen: (1) Die frühere Frage hatte Besonderheiten über die dynamische Speicherzuweisung, die nicht auf diese Frage zutreffen, und (2) diese Frage ist STL-spezifisch was kann Rufen Sie andere Idiome wie Insert-Iteratoren auf. Außerdem nutzen einige Implementierungen von STL bereits C++ 0x-r-Wert-Referenzen, um das Kopieren zu minimieren. Diese Methode ist in der früheren Frage nicht anwendbar. –

Antwort

16

Der größte Unterschied besteht darin, dass der erste Weg an vorhandene Inhalte angehängt wird, während die anderen beiden einen leeren Vektor füllen. :)

Ich denke, das Stichwort, das Sie suchen, ist return value optimization, die ziemlich häufig sein sollte (mit G ++ müssen Sie es ausschalten, um zu verhindern, dass es angewendet wird). Das heißt, wenn die Nutzung ist wie:

vector<int> vec = fill_vector(); 

dann gibt ganz einfach keine Kopien gemacht werden könnte (und die Funktion ist nur einfacher zu bedienen).

Wenn Sie mit einem bestehenden Vektor

vector<int> vec; 
while (something) 
{ 
    vec = fill_vector(); 
    //do things 
} 

dann mit einem Out-Parametern arbeiten würde um in einer Schleife und das Kopieren von Daten Erstellung von Vektoren vermeiden.

+1

+1 für den Unterschied zwischen Initialisierung und Zuordnung w.r.t. Kopie Elision. – sellibitze

2

Die Zuerst kopiere definitiv den Vektor nicht.

Die Kosten für das Kopieren des Vektors könnten linear in der Anzahl der Elemente im Vektor sein.

Die erste führt kein Risiko für lineares Verhalten auf irgendeiner Plattform oder Compiler und keine Kosten für Profiling und Refactoring.

+0

Die erste ermöglicht es auch, mehrere Funktionen in einer Reihe aufzurufen, um einen einzelnen Vektor zu füllen. Wenn Sie dasselbe mit # 2 machen müssen, müssen Sie Elemente aus dem Vektor kopieren, der zurückgegeben wird (was bedeutet, dass alle heroischen RVO-Bemühungen des Compilers umsonst sind, da Sie sofort mit einem verworfene Kopie sowieso). –

6

Man würde annehmen, Parameter ist am besten, aber in der Praxis ist es oft nicht. Dies hängt vom Compiler ab. Einige Compiler (ich denke eigentlich die neuesten Compiler) werden angewendet Return Value Optimization - Visual Studio 2005 und später sollte es in beiden Fällen tun, die Sie zur Verfügung gestellt haben (siehe Named Return Value Optimization in Visual C++ 2005).

Der beste Weg, um sicher zu sein, ist die Demontage zu überprüfen.

+0

Dies ist eine _große_ Optimierung, und die meisten modernen Compiler nutzen sie mit großen Leistungssteigerungen. – fbrereto

+0

Die ganze Idee von RVO lässt mich zusammenzucken. Ich hasse es. –

0

Auf einen Blick, die ersten beiden haben wahrscheinlich mehr Größenanpassung des Vektors, während der dritte wahrscheinlich nicht die Größe des Vektors ändern muss, wie es läuft. Dies kann gemildert werden, indem Sie es vor den Pushbacks selbst anpassen.

5

eine vierte Variante in den Mix:

void FillVector_4(vector<int>& v) { 
    static const int tab[SZ] = {1,2,3, ... }; 
    v.assign(tab,tab+SZ); 
} 

Wenn Sie sich über die Leistung der Version 2 und 3 denken kann der Compiler eine vector<int> Kopie für den Rückgabewert machen erstellen. Das heißt, wenn der Compiler NRVO nicht ausführen kann (benannte Rückgabewertoptimierung). Auch konsekutiv push_back s ohne reserve führt wahrscheinlich zu ein paar Neuzuweisungen, da der Vektor wachsen muss. Ob das überhaupt zählt, hängt von Ihrem Problem ab, das Sie lösen möchten.

Sie werden erfreut sein, dass C++ 0x die Rückgabe eines lokal erstellten Vektors sehr effizient macht. Ich empfehle auch David Abrahams article series über effiziente Werttypen einschließlich Weitergabe/Rückgabe.

9

Der idiomatische C++ Ansatz würde über den Behältertyp abstrakt durch einen Ausgang Iterator:

template<typename OutputIterator> 
void FillContainer(OutputIterator it) { 
    *it++ = 1; 
    ... 
} 

Dann kann es wie mit Vektor verwendet werden:

std::vector<int> v; 
FillContainer(std::back_inserter(v)); 

Die Leistung (und andere Vorteile wie das Füllen eines nicht leeren Containers) sind die gleichen wie für Ihre Option # 1. Eine andere gute Sache ist, dass dies verwendet werden kann, um in einem Streaming-Modus auszugeben, wo Ergebnisse sofort verarbeitet und verworfen werden, ohne gespeichert zu werden, wenn die geeignete Art von Iterator verwendet wird (z. B. ostream_iterator).

+0

+1. Dies scheint der "STL-Weg" zu sein. Beachten Sie, dass es nützlich sein könnte, den Iterator von 'FillContainer' zurückzugeben. –

+0

Wie funktioniert das, wenn "FillContainer" eine Methode einer Klasse ist? – User

+0

@User: Der gleiche Weg - Klassen-Member-Funktionen können auch templated sein. Was Sie mit so etwas nicht tun können, ist, es virtuell zu machen. –