2012-11-24 11 views
7

cercavo di adattare un codice e spostando il contenuto da un vettore all'altro usando emplace_back()C++ 11 - emplace_back tra 2 vettori non funziona

#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); 
} 

Questo codice non viene compilato con g ++ -4.7, g ++ - 4.6 e clang ++: cosa c'è di sbagliato in questo?

ho sempre avuto 1 errore principale circa

chiamata a implicitamente-cancellato costruttore di copia di obj

?

+2

Il messaggio di errore indica che 'obj' ha bisogno di un costruttore di copie. – Mat

+0

@mat perché deve essere costruito? non dovrebbe usare il costruttore di mosse + l'operatore di assegnazione del movimento? – user1849534

+0

Controlla la definizione di [emplace_back] (http://en.cppreference.com/w/cpp/container/vector/emplace_back), hai bisogno di un costruttore che prenda ciò che stai passando 'emplace_back'. Stai passando un valore-l e non hai un costruttore che corrisponda. – Mat

risposta

9

Sebbene la risposta esistente fornisca una soluzione alternativa utilizzando std::move per la compilazione del programma, è necessario dire che l'utilizzo di emplace_back sembra essere basato su un malinteso.

Il modo in cui lo descrivete ("Stavo cercando di [...] spostare il contenuto da un vettore ad un altro utilizzando emplace_back()") e il modo in cui lo si utilizza suggeriscono che si pensa di emplace_back come metodo a sposta elementi nel vettore e di push_back come metodo per copia elementi in un vettore.Il codice utilizzato per riempire la prima istanza del vettore sembra suggerire anche questo:

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

Ma questo non è ciò che la differenza tra emplace_back e push_back è circa.

primo luogo, anche push_back si spostare (non copiare) gli elementi nel vettore se solo viene dato un rvalue, e se il tipo di elemento ha un operatore di assegnazione mossa.

In secondo luogo, il vero caso d'uso di emplace_back è quello di costruire elementi in luogo, vale a dire lo si usa quando si vuole mettere gli oggetti in un vettore che non esiste ancora. Gli argomenti di emplace_back sono gli argomenti per il costruttore dell'oggetto. Così il ciclo di cui sopra dovrebbe davvero guardare in questo modo:

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

Il motivo per cui il codice esistente funziona è che obj("Jon") è anche un argomento valido per il costruttore (in particolare, al costruttore mossa). Ma l'idea principale di emplace_back è che non è necessario creare l'oggetto e quindi spostarlo. Non si beneficia di tale idea quando si passa obj("Jon") anziché "Jon".

D'altra parte, nel secondo ciclo si tratta di oggetti creati in precedenza. Non ha senso usare emplace_back per spostare oggetti già esistenti. E ancora, emplace_back applicato a un oggetto esistente non significa che l'oggetto è stato spostato. Significa solo che è creato sul posto, usando il normale costruttore di copie (se esiste). Se si desidera spostarlo, è sufficiente utilizzare push_back, applicata al risultato di std::move:

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 
} 

Ulteriori note
1) È possibile semplificare il ciclo sopra utilizzando C++ 11 gamma-based per:

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

2) Indipendentemente dal fatto che si utilizza un normale ciclo for o gamma-based per, si muovono gli elementi uno per uno, il che significa che la fonte vettore v rimarrà come un vettore di 1000 oggetti vuoti. Se si vuole realmente cancellare il vettore nel processo (ma ancora utilizzare la semantica si muovono per trasportare gli elementi per il nuovo vettore), è possibile utilizzare il costruttore mossa del vettore stesso:

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

Questo riduce il secondo ciclo a una sola riga, e si assicura che il vettore sorgente sia cancellato.

+0

grazie! una spiegazione davvero buona, ho solo una cosa in più da spiegare, l'istruzione 'v.emplace_back (obj (" Jon "));' nel ciclo 'for' funziona a causa del meccanismo di risoluzione dell'ottica che seleziona il giusto (o sbagliato nel mio caso) costruttore? – user1849534

+0

Oh, funziona perché 'obj (" Jon ")' è un valore rvalore (perché è un oggetto temporaneo). Quindi 'emplace_back' chiama il costruttore che accetta riferimenti rvalue,' obj :: obj (obj && tmp) '. Se avevate prima memorizzato l'oggetto in una variabile (es. Un lvalue) come questo: 'obj x = obj (" Jon ");' e poi chiamato 'emplace_back (x);' quindi non avrebbe funzionato perché 'x' non è un valore. Invece avrebbe cercato un costruttore 'obj (obj & tmp)' o 'obj (const obj & tmp)', ma non ne avrebbe trovato nessuno, quindi suppongo che avrebbe fallito. – jogojapan

7

Il problema è che

p.emplace_back(v[i]); 

passa un lvalue a emplace_back, che significa che il costruttore mossa (che prevede un riferimento rvalue) non funziona.

Se effettivamente desidera spostare i valori da un contenitore all'altro, si dovrebbe chiamare esplicitamente std::move:

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

(L'idea alla base di un costruttore mossa come obj(obj&& tmp) è che tmp dovrebbe essere un oggetto che non è nel tuo ciclo primo, si passa un oggetto temporaneo a emplace_back, il che va bene: un riferimento di rvalue può legarsi a un oggetto temporaneo e rubare i dati da esso perché l'oggetto temporaneo sta per sparire Nel tuo ciclo secondo, l'oggetto che si passa a emplace_back ha un nome: v[i]. Ciò significa che non è temporaneo e potrebbe essere indicato più avanti nel programma. Ecco perché è necessario utilizzare std::move per dire al compilatore)


Modifica "sì, ho davvero intenzione di rubare i dati da questo oggetto, anche se qualcun altro potrebbe tentare di utilizzare in seguito.": Presumo che il tuo uso piuttosto insolito di emplace_back sia un residuo di dover creare un piccolo esempio per noi. Se questo non è il caso, vedere la risposta di @jogojapan per una buona discussione sul perché utilizzare un costruttore di spostamenti std::vector o chiamate ripetute a push_back avrebbe più senso per il tuo esempio.

+0

Ero convinto che v [i] sia un Rvalue, poiché si tratta di un riferimento a una posizione di un tipo ben definito, strano sapere che è un lvalue. ma la tua soluzione funziona ... – user1849534

+0

@ user1849534: Ho aggiornato la risposta per cercare di spiegarlo un po 'di più. Il suo significato è che 'v [i]' è un lvalue perché è un oggetto con nome a cui si potrebbe fare riferimento più tardi. –