2014-09-24 16 views
8

Leggendo la risposta a this question, sono stato sorpreso di scoprire che std::min(std::initializer_list<T>) prende i suoi argomenti in base al valore. Se si utilizza std::initializer_list nel modo implicito dal suo nome, vale a dire come inizializzatore di un oggetto, capisco che non ci interessa copiare i suoi elementi poiché saranno comunque copiati per inizializzare l'oggetto. Tuttavia, in questo caso qui molto probabilmente non abbiamo bisogno di alcuna copia, quindi sembrerebbe molto più ragionevole prendere gli argomenti come std::initializer_list<const T&> se solo fosse possibile.Perché std :: min (std :: initializer_list <T>) prende gli argomenti in base al valore?

Qual è la migliore pratica per questa situazione? Non dovresti chiamare la versione initializer_list di std::min se non ti interessa fare copie non necessarie, o c'è qualche altro trucco per evitare la copia?

+0

AFAIK, l'utilizzo di un riferimento richiede più cicli della CPU che passare una copia di 'int' e richiede più memoria (almeno su x86) rispetto al passaggio di variabili più piccole. – GingerPlusPlus

+0

Vero, ma std :: min non è limitato a int (o qualsiasi tipo numerico). E per oggetti di grandi dimensioni può essere * molto * importante rinunciare alla copia. – gTcV

+1

Penso che la ragione sia descritta in [N2722] (http://open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2722.pdd), anche se sono riluttante a fidarmi di quel confronto di prestazioni e la conclusione. Potrebbe esserci più discussione registrata da qualche parte. – dyp

risposta

2

riassumere i commenti:

Un std::initializer_list si suppone essere un proxy leggero come descritto a cppreference.com. Pertanto una copia di una lista di inizializzazione dovrebbe essere molto veloce in quanto gli elementi sottostanti non vengono copiati:

C++ 11 §18.9/2

Un oggetto di tipo initializer_list<E> fornisce accesso ad un array di oggetti del tipo [Nota: Una coppia di puntatori o un puntatore più una lunghezza sarebbero rappresentazioni ovvie per initializer_list. initializer_list viene utilizzato per implementare gli elenchi di inizializzatori come specificato in 8.5.4. La copia di un elenco di inizializzazione non copia gli elementi sottostanti. - nota fine]

Usare un riferimento se si ridurrebbe a utilizzare un puntatore e quindi un ulteriore indiretto.

Il problema attuale di std::min quindi non è che prende il initializer_list per valore, ma piuttosto, che gli argomenti a initializer_list devono essere copiati se sono calcolate in fase di esecuzione. [1] Che il benchmark in [1] fosse rotto come rilevato later è sfortunato.

auto min_var = std::min({1, 2, 3, 4, 5}); // fast 
auto vec = std::vector<int>{1, 2, 3, 4, 5}; 
min_var = std::min({vec[0], vec[1], vec[2], vec[3], vec[4]}); // slow 

[1]: N2722 p. 2

+0

Penso che tu abbia completamente frainteso sia la domanda che i commenti. Essi lamentano che gli elementi in 'initializer_list' vengono copiati in fase di runtime, piuttosto che avere un' initializer_list' di riferimenti. –

+0

Buon punto! Ho trascurato il link "mostra 8 altri commenti", che ha provocato un caos di modifica una volta che l'ho visto. Anche se non direi che ho frainteso la domanda. Perché l'elenco di inizializzazione non è necessario creare copie quando viene creato.Questo dipende dalla situazione. – mfuchs

+0

Il tuo "benchmark" sembra sostenere che copiare un 'int' è lento. Non dovrebbe essere un vettore di grandi oggetti costosi? (Ok, vedo che l'articolo collegato ha esattamente questo, ognuno è un 'std :: list' di milioni di elementi) –

5

Non esiste una cosa come std::initializer_list<const T&>. Gli elenchi di inizializzatori possono contenere solo oggetti per valore.

Sede [dcl.init.list]/5:

Un oggetto di tipo std::initializer_list<E> è costruito da una lista di inizializzazione come se l'attuazione assegnato un array temporaneo di N elementi di tipo const E, dove N è il numero di elementi nella lista di inizializzazione.

Non ci sono matrici di riferimenti, quindi non ci può essere uno initializer_list di riferimenti.

Il mio suggerimento in questa circostanza sarebbe quello di scrivere un sostituto per std::min che prende un modello variadic di riferimenti di inoltro. Questo funziona (anche se sono sicuro che può essere migliorato):

template<typename T, typename... Args> 
T vmin(T arg1, Args&&... args) 
{ 
    T *p[] = { &arg1, &args... }; 

    return **std::min_element(begin(p), end(p), 
      [](T *a, T *b) { return *a < *b; }); 
} 

See it working - utilizzando min o mmin, due copie in più sono fatte (per a e b).