2010-09-13 1 views
10

Eventuali duplicati:
how to “return an object” in C++C vettore ++, di ritorno contro parametro

Mi chiedo se c'è una differenza tra le seguenti tre approcci:

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 prestazioni? – Anycorn

+4

Restituisci semplicemente per valore e ti preoccupi delle prestazioni quando ritieni che sia effettivamente un problema. Quindi traccia il profilo in modo da non dover indovinare. Sto chiudendo preventivamente come [duplicato] (http://stackoverflow.com/questions/3350385/how-to-return-an-object-in-c). – GManNickG

+1

Non avrei votato per chiuderlo come duplicato per un paio di motivi: (1) La domanda precedente aveva specifiche sull'allocazione dinamica della memoria che non si applicano a questa domanda, e (2) questa domanda è specifica per STL che può invoca altri idiomi come inserire gli iteratori. Inoltre, alcune implementazioni di STL già sfruttano i riferimenti a valore r di C++ 0x per ridurre al minimo la copia. Questo metodo non si applica alla domanda precedente. –

risposta

16

La più grande differenza è che il primo modo si aggiunge ai contenuti esistenti, mentre gli altri due riempiono un vettore vuoto. :)

Penso che la parola chiave che stai cercando sia return value optimization, che dovrebbe essere piuttosto comune (con G ++ dovrai spegnerlo specificatamente per evitare che venga applicato). Cioè, se l'utilizzo è simile:

vector<int> vec = fill_vector(); 

allora ci potrebbe abbastanza facilmente esserci copie (e la funzione è solo più facile da usare).

Se si lavora con un vettore esistente

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

quindi utilizzando un parametro out eviterebbe la creazione di vettori in un ciclo e la copia dei dati in giro.

+1

+1 per indicare la differenza tra inizializzazione e assegnazione w.r.t. copia elisione. – sellibitze

2

Il prima di tutto non copia il vettore.

Il costo della copia del vettore potrebbe essere lineare nel numero di elementi nel vettore.

Il primo non introduce alcun rischio di comportamento lineare su alcuna piattaforma o compilatore e nessun costo di profilazione e refactoring.

+0

Il primo consente inoltre di chiamare più funzioni di seguito per riempire un singolo vettore. Se hai bisogno di fare lo stesso con # 2, dovrai copiare gli elementi dal vettore che viene restituito (il che significa che tutti gli sforzi eroici del RVO da parte del compilatore saranno inutili, dal momento che finirai con un copia scartata comunque). –

6

Si presume che il parametro sia il migliore, ma in pratica spesso non lo è. Questo dipende dal compilatore. Alcuni compilatori (penso che in realtà i compilatori più recenti) si applicheranno a Return Value Optimization - Visual Studio 2005 e versioni successive dovrebbero eseguirlo in entrambi i casi forniti (vedere Named Return Value Optimization in Visual C++ 2005).

Il modo migliore per sapere con certezza è controllare lo smontaggio prodotto.

+0

Questa è una _huge_ ottimizzazione, e la maggior parte dei compilatori moderni la utilizzano con grandi vantaggi in termini di prestazioni. – fbrereto

+0

L'intera idea di RVO mi fa rabbrividire. Lo odio. –

0

In breve, i primi due probabilmente hanno più ridimensionamento del vettore in corso, mentre il terzo probabilmente non ha bisogno di ridimensionare il vettore mentre corre. Questo può essere mitigato ridimensionandolo prima dei pushback.

5

L'aggiunta di una quarta variante nel mix:

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

Se stai pensando di prestazioni versione 2 e versione 3 può rendere il compilatore creare una copia vector<int> per il valore di ritorno. Cioè, se il compilatore non è in grado di fare NRVO (denominato ottimizzazione del valore di ritorno). Inoltre, i successivi push_back s senza uno reserve probabilmente portano a un paio di riallocazioni dal momento che il vettore deve crescere. Se questo è importante dipende dal tuo problema che stai cercando di risolvere.

Sarai felice di sapere che C++ 0x renderà molto efficiente il ritorno di un vettore creato localmente. Raccomando anche di leggere David Abrahams article series sui tipi di valore efficienti incluso il passaggio/il ritorno.

9

L'approccio ++ C idiomatica sarebbe astratta sul tipo di contenitore utilizzando un iteratore uscita:

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

Quindi può essere utilizzata con vettore come:

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

Le prestazioni (ed altri i vantaggi, come essere in grado di riempire un contenitore non vuoto) sono gli stessi della tua opzione # 1. Un altro aspetto positivo è che può essere utilizzato per l'output in modalità streaming, dove i risultati vengono immediatamente elaborati e scartati senza essere memorizzati, se viene utilizzato il tipo appropriato di iteratore (ad esempio ostream_iterator).

+0

+1. Questo sembra il modo più "STL". Si noti che potrebbe essere utile restituire l'iteratore da 'FillContainer'. –

+0

Come funziona quando "FillContainer" è un metodo di una classe? – User

+0

@User: allo stesso modo, le funzioni dei membri della classe possono anche essere basate su modelli. Ciò che non puoi fare con una cosa del genere è renderlo virtuale. –