2012-11-24 16 views
7

Ich habe versucht, einige Codes anzupassen und den Inhalt von einem Vektor zu einem anderen zu bewegen mit emplace_back()C++ 11 - emplace_back zwischen zwei Vektoren nicht funktioniert

#include <iostream> 
#include <vector> 

struct obj 
{ 
    std::string name; 

    obj():name("NO_NAME"){} 
    obj(const std::string& _name):name(_name){} 

    obj(obj&& tmp): name(std::move(tmp.name)) {} 
    obj& operator=(obj&& tmp) = default; 

}; 

int main(int argc, char* argv[]) 
{ 

    std::vector<obj> v; 
    for(int i = 0; i < 1000; ++i) 
    { 
    v.emplace_back(obj("Jon")); 
    } 

    std::vector<obj> p; 
    for(int i = 0; i < 1000; ++i) 
    { 
    p.emplace_back(v[i]); 
    } 

    return(0); 
} 

Dieser Code nicht mit g ++ nicht kompiliert -4.7, g ++ - 4.6 und clang ++: was ist daran falsch?

Ich habe immer einen Hauptfehler über

Anruf Copykonstruktor von obj

to-implizit gelöscht?

+2

Die Fehlermeldung besagt, dass 'obj' einen Kopierkonstruktor benötigt. – Mat

+0

@mat warum muss es bauen? Es sollte nicht der Bewegungskonstruktor + der Bewegungszuweisungsoperator verwendet werden? – user1849534

+0

Überprüfen Sie die Definition von [emplace_back] (http://en.cppreference.com/w/cpp/container/vector/emplace_back), Sie benötigen einen Konstruktor, der die übergebene 'emplace_back' übernimmt. Sie übergeben einen l-Wert und haben keinen übereinstimmenden Konstruktor. – Mat

Antwort

9

Obwohl die vorhandene Antwort eine Problemumgehung mit std::move bietet, die Ihr Programm kompiliert, muss gesagt werden, dass Ihre Verwendung von emplace_back scheint auf einem Missverständnis beruhen.

Die Art und Weise Sie es beschreiben („Ich war zu versuchen, [...] Bewegen des Inhalts von einem Vektor zu einem anderen mit emplace_back()) und wie Sie es vorschlagen, verwenden, dass Sie von emplace_back als Methode denken zu Verschieben Sie Elemente in den Vektor, und von push_back als eine Methode zu kopieren Sie Elemente in einen Vektor.Der Code, den Sie die erste Instanz des Vektors verwenden zu füllen scheint dies auch vorschlagen:

std::vector<obj> v; 
for(int i = 0; i < 1000; ++i) 
{ 
    v.emplace_back(obj("Jon")); 
} 

Aber das ist nicht das, was der Unterschied zwischen emplace_back und push_back geht.

Erstens wird selbst push_back in den Vektor (nicht kopiert), um die Elemente zu bewegen, wenn es nur einen R-Wert gegeben wird, und wenn der Elementtyp einen Zug Zuweisungsoperator hat.

Zweitens die reale Anwendungsfall von emplace_back ist Elemente zu konstruieren anstelle, das heißt Sie es verwenden, wenn Sie Objekte in einen Vektor möchten, die noch nicht vorhanden sind. Die Argumente von emplace_back sind die Argumente für den Konstruktor des Objekts. So Ihre Schleife oben sollte wirklich so aussehen:

std::vector<obj> v; 
for(int i = 0; i < 1000; ++i) 
{ 
    v.emplace_back("Jon"); // <-- just pass the string "Jon" , not obj("Jon") 
} 

Der Grund, warum Ihr vorhandener Code funktioniert ist, dass obj("Jon") ist auch ein gültiges Argument für den Konstruktor (genauer gesagt, den Umzug Konstruktor). Aber die Grundidee von emplace_back ist, dass Sie das Objekt nicht erstellen und dann verschieben müssen. Sie profitieren nicht von dieser Idee, wenn Sie statt "Jon" zu ihm übergeben.

Auf der anderen Seite, in Ihrer zweiten Schleife beschäftigen Sie sich mit Objekten, die zuvor erstellt wurden. Es macht keinen Sinn, Objekte, die bereits existieren, mit emplace_back zu verschieben. Und wieder, emplace_back angewendet auf ein vorhandenes Objekt bedeutet nicht, dass das Objekt verschoben wird. Es bedeutet nur, dass es an Ort und Stelle mit dem gewöhnlichen Kopierkonstruktor erstellt wird (falls vorhanden). Wenn Sie möchten, um es zu bewegen, einfach push_back verwenden, auf das Ergebnis der std::move angewendet:

std::vector<obj> p; 
for(int i = 0; i < 1000; ++i) 
{ 
    p.push_back(std::move(v[i])); // <-- Use push_back to move existing elements 
} 

Weitere Hinweise
1) Sie die Schleife vereinfachen kann C++ 11 oben mit bereichsbasierte für:

std::vector<obj> p; 
for (auto &&obj : v) 
    p.push_back(std::move(obj)); 

2) Unabhängig davon, ob sie für eine gewöhnliche Steuerung oder bereichsbasierten Anwendungsgebiete bewegen Sie die eine Elemente nach dem anderen, was bedeutet, dass der Quellenvektor v als ein Vektor von 1000 leer Objekte verbleiben. Wenn Sie tatsächlich den Vektor in dem Prozess löschen wollen (aber immer noch Semantik verwenden bewegt die Elemente auf den neuen Vektor zu transportieren), können Sie den Umzug Konstruktor des Vektors selbst verwenden:

std::vector<obj> p(std::move(v)); 

dies die zweite Schleife reduziert auf nur eine einzige Zeile, und es stellt sicher, dass der Quellvektor gelöscht wird.

+0

danke! eine wirklich gute Erklärung, ich habe nur noch eine Sache zu erklären, die Anweisung 'v.emplace_back (obj (" Jon "));' im 'for'-Zyklus funktioniert wegen des Mechanismus zur Auflösung des Bereichs, der das richtige (oder falsche) auswählt in meinem Fall) Konstruktor? – user1849534

+0

Oh, es funktioniert, weil 'obj (" Jon ")' ein rvalue ist (weil es ein temporäres Objekt ist). So ruft 'emplace_back' den Konstruktor auf, der rvalue-Referenzen akzeptiert,' obj :: obj (obj && tmp) '. Wenn Sie zuerst das Objekt in einer Variablen (dh einem L-Wert) wie folgt gespeichert hätten: 'obj x = obj (" Jon ");' und dann 'emplace_back (x); 'dann hätte es nicht funktioniert, weil' x' ist kein rvalue. Stattdessen hätte es nach einem Konstruktor "obj (obj & tmp)" oder "obj (const obj & tmp)" gesucht, aber es hätte keinen gefunden, also nehme ich an, es wäre fehlgeschlagen. – jogojapan

7

Das Problem ist, dass

p.emplace_back(v[i]); 

gibt einen L-Wert zu emplace_back, was bedeutet, dass Ihr Umzug Konstruktor (die eine rvalue Referenz erwartet) wird nicht funktionieren.

Wenn Sie tatsächlich von einem Behälter in einen anderen verschieben Werte wollen, sollten Sie explizit std::move nennen:

p.emplace_back(std::move(v[i])); 

(Die Idee hinter einem Umzug Konstruktor wie obj(obj&& tmp) ist, dass tmp ein Objekt sein sollte, die nicht ist In Ihrer ersten Schleife übergeben Sie ein temporäres Objekt an emplace_back, was in Ordnung ist - eine Rvalue-Referenz kann an ein temporäres Objekt binden und Daten daraus stehlen, weil das temporäre Objekt im Begriff ist zu verschwinden In Ihrer Sekunde Schleife, Das Objekt, das Sie an emplace_back übergeben, hat einen Namen: v[i]. Das heißt, es ist nicht temporär und könnte später im Programm erwähnt werden. Deshalb haben Sie std::move zu verwenden, um den Compiler zu sagen)


Edit „ja, ich Daten wirklich von diesem Objekt gemeint zu stehlen, auch wenn jemand anderes könnte versuchen, es später zu verwenden.“: Ich nehme an, dass Ihre eher ungewöhnliche Verwendung von emplace_back ein Relikt davon ist, ein kleines Beispiel für uns zu schaffen. Wenn dies nicht der Fall ist, finden Sie in der @ Jogojapan-Antwort eine gute Diskussion darüber, warum die Verwendung eines std::vector Move-Konstruktors oder wiederholter Aufrufe an push_back für Ihr Beispiel sinnvoller wäre.

+0

Ich war davon überzeugt, dass es sich bei v [i] um einen Rvalue handelt, da es sich um eine Position eines gut definierten Typs handelt, die merkwürdig ist zu wissen, dass es sich um einen Lvalue handelt. aber Ihre Lösung funktioniert ... – user1849534

+0

@ user1849534: Ich habe die Antwort aktualisiert, um es ein wenig mehr zu erklären versuchen. Der Kern davon ist, dass 'v [i]' ein Lvalue ist, weil es ein benanntes Objekt ist, auf das Sie später verweisen können. –