it-swarm.com.de

Effiziente Möglichkeit, einen std :: vector in c ++ zurückzugeben

Wie viele Daten werden kopiert, wenn ein std :: vector in einer Funktion zurückgegeben wird, und wie groß eine Optimierung sein wird, wenn der std :: vector im freien Speicher (auf dem Heap) abgelegt wird und stattdessen ein Zeiger zurückgegeben wird.

std::vector *f()
{
  std::vector *result = new std::vector();
  /*
    Insert elements into result
  */
  return result;
} 

effizienter als:

std::vector f()
{
  std::vector result;
  /*
    Insert elements into result
  */
  return result;
} 

?

34
Morten

In C++ 11 ist dies der bevorzugte Weg:

std::vector<X> f();

Das heißt, Rückgabe nach Wert. 

Mit C++ 11 verfügt std::vector über eine Move-Semantik, dh der in Ihrer Funktion deklarierte local vector wird verschoben bei der Rückkehr und in einigen Fällen kann sogar der Zug vom Compiler entfernt werden.

55
Nawaz

Sie sollten nach Wert zurückkehren.

Der Standard verfügt über eine spezifische Funktion, um die Effizienz der Rückgabe nach Wert zu verbessern. Es heißt "Copy Elision" und in diesem Fall genauer gesagt die "Named Return Value Optimization (NRVO)".

Compiler müssen es nicht implementieren, aber Compiler verwenden have nicht, um das Inlining von Funktionen zu implementieren (oder überhaupt eine Optimierung durchzuführen). Die Leistung der Standardbibliotheken kann jedoch sehr schlecht sein, wenn Compiler nicht optimiert werden und alle seriösen Compiler Inlining und NRVO (und andere Optimierungen) implementieren.

Wenn NRVO angewendet wird, wird der folgende Code nicht kopiert:

std::vector<int> f() {
    std::vector<int> result;
    ... populate the vector ...
    return result;
}

std::vector<int> myvec = f();

Möglicherweise möchte der Benutzer dies jedoch tun:

std::vector<int> myvec;
... some time later ...
myvec = f();

Das Kopieren von Kopien verhindert hier keine Kopie, da es sich um eine Zuweisung anstatt um eine Initialisierung handelt. Sie sollten jedoch noch nach Wert zurückgeben. In C++ 11 wird die Zuordnung durch etwas anderes optimiert, das als "Move Semantics" bezeichnet wird. In C++ 03 verursacht der obige Code eine Kopie, und obwohl theoretisch ein Optimierer dies möglicherweise vermeiden kann, ist dies in der Praxis zu schwierig. Anstelle von myvec = f() sollten Sie also in C++ 03 Folgendes schreiben:

std::vector<int> myvec;
... some time later ...
f().swap(myvec);

Es gibt eine weitere Möglichkeit, dem Benutzer eine flexiblere Oberfläche anzubieten:

template <typename OutputIterator> void f(OutputIterator it) {
    ... write elements to the iterator like this ...
    *it++ = 0;
    *it++ = 1;
}

Sie können darüber hinaus auch die vorhandene vektorbasierte Schnittstelle unterstützen:

std::vector<int> f() {
    std::vector<int> result;
    f(std::back_inserter(result));
    return result;
}

Dieses möglicherweise ist weniger effizient als Ihr vorhandener Code, wenn reserve() in Ihrem vorhandenen Code komplexer ist als nur ein fester Betrag. Wenn Ihr bestehender Code jedoch im Prinzip wiederholt Push_back für den Vektor aufruft, sollte dieser vorlagenbasierte Code so gut sein.

31
Steve Jessop

Es ist Zeit, dass ich eine Antwort über RVO schreibe, ich auch ...

Wenn Sie ein Objekt nach Wert zurückgeben, optimiert der Compiler dies häufig, sodass es nicht zweimal erstellt wird, da es überflüssig ist, es in der Funktion als temporär zu erstellen und dann zu kopieren. Dies wird als Rückgabewertoptimierung bezeichnet: Das erstellte Objekt wird verschoben und nicht kopiert.

3
user529758
vector<string> getseq(char * db_file)

Und wenn Sie es auf main () drucken möchten, sollten Sie es in einer Schleife machen.

int main() {
     vector<string> str_vec = getseq(argv[1]);
     for(vector<string>::iterator it = str_vec.begin(); it != str_vec.end(); it++) {
         cout << *it << endl;
     }
}
0
Akash Kandpal

Ja, Rückgabe nach Wert. Der Compiler kann damit automatisch umgehen.

0
bruce

Ein üblicher Ausdruck ist das Übergeben einer Referenz auf das Objekt, das gefüllt wird.

Dann wird der Vektor nicht kopiert.

void f( std::vector & result )
{
  /*
    Insert elements into result
  */
} 
0
Drew Dormann

Wenn der Compiler Named Return Value Optimization ( http://msdn.Microsoft.com/de-de/library/ms364057(v=vs.80).aspx ) unterstützt, können Sie den Vektor direkt zurückgeben, vorausgesetzt, es gibt kein:

  1. Verschiedene Pfade, die unterschiedliche benannte Objekte zurückgeben
  2. Mehrere Rückgabepfade (auch wenn dasselbe benannte Objekt in allen Pfaden zurückgegeben wird) mit EH-Status.
  3. Das zurückgegebene benannte Objekt wird in einem Inline-ASM-Block referenziert.

NRVO optimiert die Aufrufe des redundanten Kopierkonstruktors und des Destruktors und verbessert so die Gesamtleistung. 

In Ihrem Beispiel sollte es keinen echten Unterschied geben.

0
taocp

So schön wie "return by value" auch sein mag, es ist die Art von Code, die zu Fehlern führen kann. Betrachten Sie das folgende Programm:

    #include <string>
    #include <vector>
    #include <iostream>
    using namespace std;
    static std::vector<std::string> strings;
    std::vector<std::string> vecFunc(void) { return strings; };
    int main(int argc, char * argv[]){
      // set up the vector of strings to hold however
      // many strings the user provides on the command line
      for(int idx=1; (idx<argc); ++idx){
         strings.Push_back(argv[idx]);
      }

      // now, iterate the strings and print them using the vector function
      // as accessor
      for(std::vector<std::string>::interator idx=vecFunc().begin(); (idx!=vecFunc().end()); ++idx){
         cout << "Addr: " << idx->c_str() << std::endl;
         cout << "Val:  " << *idx << std::endl;
      }
    return 0;
    };
  • F: Was passiert, wenn das obige ausgeführt wird? A: Ein Coredump 
  • F: Warum hat der Compiler den Fehler nicht gefunden? A: Weil das Programm syntaktisch, jedoch nicht semantisch, korrekt ist.
  • F: Was passiert, wenn Sie vecFunc () ändern, um eine Referenz zurückzugeben? A: Das Programm wird vollständig ausgeführt und liefert das erwartete Ergebnis.
  • F: Was ist der Unterschied? A: Der Compiler muss keine anonymen Objekte erstellen und verwalten. Der Programmierer hat den Compiler angewiesen, genau ein Objekt für den Iterator und für die Endpunktbestimmung zu verwenden, anstatt zwei verschiedene Objekte zu verwenden, wie dies im abgebrochenen Beispiel der Fall ist.

Das obige fehlerhafte Programm zeigt keine Fehler an, selbst wenn Sie die Berichtsoptionen GNU g ++ -Wall -Wextra -Weffc ++ verwenden

Wenn Sie einen Wert erzeugen müssen, funktionieren die folgenden Schritte, um vecFunc () zweimal aufzurufen:

   std::vector<std::string> lclvec(vecFunc());
   for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)...

Das Obige erzeugt auch keine anonymen Objekte während der Iteration der Schleife, sondern erfordert einen möglichen Kopiervorgang (der, wie einige Hinweise, unter Umständen wegoptimiert werden. Die Referenzmethode garantiert jedoch, dass keine Kopie erzeugt wird.) Glauben Sie dem Compiler RVO ausführen ist kein Ersatz für den Versuch, den effizientesten Code zu erstellen, der möglich ist.Wenn Sie die Notwendigkeit für den Compiler für RVO bemerken, sind Sie dem Spiel voraus. 

0
   vector<string> func1() const
   {
      vector<string> parts;
      return vector<string>(parts.begin(),parts.end()) ;
   } 
0
Amruth A