2011-02-01 5 views
22

Sto scrivendo un'app per entrambe le finestre di Linux & e ho notato che il build GCC sta producendo molte chiamate inutili al costruttore di copie.Contenitori di librerie standard che producono molte copie su rvalues ​​in GCC

Ecco un codice di esempio per la produzione di questo comportamento:

struct A 
{ 
    A()    { std::cout << "default" << std::endl; } 
    A(A&& rvalue)  { std::cout << "move" << std::endl; } 
    A(const A& lvalue) { std::cout << "copy" << std::endl; } 
    A& operator =(A a) { std::cout << "assign" << std::endl; return *this; } 
}; 

BOOST_AUTO_TEST_CASE(test_copy_semantics) 
{ 
    std::vector<A> vec_a(3); 
} 

Questo test crea solo un vettore di 3 elementi. Mi aspetto 3 chiamate di costruttore predefinite e 0 copie in quanto non ci sono valori l.

In Visual C++ 2010, l'uscita è:

default 
move 
default 
move 
default 
move 

In GCC 4.4.0 (MinGW), (-O2 -std = C++ 0x), l'uscita è:

default 
copy 
copy 
copy 

Cosa sta succedendo e come lo risolvo? Le copie sono costose per la classe attuale, la costruzione predefinita e le mosse sono economiche.

+1

Hai visto l'intestazione ''? Cosa sta facendo il costruttore? Il comportamento GCC è coerente con la specifica C++ 03, in cui un oggetto viene costruito come predefinito, quindi copiato N volte. È possibile che la tua versione della libreria standard non supporti il ​​nuovo costruttore aggiunto a C++ 0x, che in default costruisce N elementi. –

+1

Poiché l'esempio utilizza le caratteristiche del linguaggio C++ 0x, suppongo che si tratti di una specifica sulle specifiche di C++ 0x, non sulle specifiche di C++ 03. –

+1

Stesso output con GCC 4.5 – tstenner

risposta

18

Entrambe le implementazioni (Visual C++ 2010 e GCC 4.4.0) sono errate. L'uscita corretta è:

default 
default 
default 

Questo è specificato in 23.3.5.1 [vector.cons]/4:

Richiede: T sarà DefaultConstructible.

L'implementazione non è consentita per supporre che A sia MoveConstructible né CopyConstructible.

+5

+1, buona presa sul lato VS di esso. –

+0

Hai ragione, la mia prossima domanda sarebbe stata il motivo per cui VC si muove per la costruzione. Si può fare qualcosa? Qualche idea se le implementazioni di stlport o ea sono più conformi? – Inverse

+0

Questo std :: lib: http://libcxx.llvm.org/ ottiene la risposta corretta, ma non è stato portato su Windows. Ci sono parti interessate a portarlo su Windows e chi potrebbe senza dubbio usare l'aiuto. –

1

Prova questo allora:

std::vector<A> vec_a; 
vec_a.reserve(3); 
for (size_t i = 0; i < 3; ++i) 
    vec_a.push_back(A()); 

cosa si sta cercando di fare è forzare il processo di inizializzazione di utilizzare costrutto + mossa per ogni valore, invece di costrutto e quindi copiare/copia/copia. Queste sono solo filosofie diverse; gli autori delle librerie non potevano sapere quale sarebbe la migliore per un dato tipo.

+4

Questo è più che diverse filosofie. La bozza C++ 0x specifica il comportamento corretto. –

+1

+1, il codice proposto è in realtà una soluzione alternativa. –

6

Sembra che il problema è che la versione di g ++ che si possiede non ha una libreria completamente compatibile con C++ 0x. In particolare, in C++ 03, il costruttore dimensioni di std :: vector ha la seguente firma:

// C++ 03 
explicit vector(size_type n, const T& value = T(), 
const Allocator& = Allocator()); 

Con quella firma della funzione e la chiamata, un temporaneo viene creato, quindi vincolato dalla riferimento e copie costante ne sono creati per ciascuno degli elementi.

mentre in C++ 0x ci sono diversi costruttori:

// C++0x 
explicit vector(size_type n); 
vector(size_type n, const T& value, const Allocator& = Allocator()); 

In questo caso, la chiamata verrà corrisponde alla prima firma, e gli elementi di default dovrebbe essere costruito con nuova collocazione sopra il contenitore (come @ Howard Hinnant sottolinea correttamente nel suo answer che il compilatore non dovrebbe chiamare affatto il costruttore di mosse).

Si può provare e verificare se le versioni più recenti di g ++ hanno un aggiornati libreria standard, oppure è possibile aggirare il problema aggiungendo manualmente gli elementi:

std::vector<A> v; 
v.reserve(3);  // avoid multiple relocations 
while (v.size() < 3) v.push_back(A()); 
1

È possibile aggiungere caso speciale (a basso costo) per copiare l'algoritmo del ctor quando si copia l'oggetto predefinito costruito su "questo" oggetto. È solo una soluzione, tuttavia, il comportamento è abbastanza strano. Entrambi i compilatori (librerie) creano un oggetto temporaneo nello stack, quindi gcc copia questo temporaneo nelle destinazioni 3 volte; msvc ricrea l'oggetto temporaneo 3 volte (!) (anche in pila) e si sposta 3 volte in target. Non capisco perché non creano oggetti direttamente sul posto.

0

Penso che tutte e 3 le varianti non violino la bozza C++ 0x. Richiede seguente: 1. Costruisce un vettore di n elementi di valore inizializzato 2. T è DefaultConstructible 3. lineare n

Tutti 3 varianti soddisfano 1, come difetto + copia, default + muovere sono equivalenti a default Tutte e 3 le varianti soddisfano 3 Tutte e 3 le varianti soddisfano 2: funzionano per i tipi DefaultConstructible. Algoritmo specifico può essere utilizzato per i tipi di Moveable. È prassi generale in AWL utilizzare diverse versioni di algoritmi per tipi con capacità diverse.

Problemi correlati