2013-03-17 11 views
27

Mi sono chiesto quali sono i vantaggi degli argomenti variadici sugli elenchi di inizializzatori. Entrambi offrono la stessa capacità - per passare un numero indefinito di argomenti a una funzione.Perché utilizzare argomenti variadici ora quando sono disponibili gli elenchi di inizializzatori?

Quello che personalmente penso è che le liste di inizializzazione sono un po 'più eleganti. La sintassi è meno imbarazzante.

Inoltre, sembra che gli elenchi di inizializzatori abbiano prestazioni significativamente migliori all'aumentare del numero di argomenti.

Quindi cosa mi manca, oltre alla possibilità di utilizzare anche argomenti variadici in C?

+13

Gli elenchi di inizializzatori possono avere solo un tipo. Tieni presente che ci sono modelli variadici, a differenza degli argomenti variati C non di tipo sicuro. – chris

+0

@chris: Ed è anche un peccato. :( – GManNickG

+0

@GManNickG, Sì, 'std :: tuple' incorporato nelle liste di inizializzazione sarebbe bello, ma ci sono indubbiamente cose che sarebbero difficili da risolvere che non riesco a pensare proprio adesso. – chris

risposta

45

Se da argomenti variadic intendi i puntini di sospensione (come in void foo(...)), poi quelli sono fatti più o meno obsoleti dalla modelli variadic piuttosto che dalle liste di inizializzazione - c'è ancora potrebbero essere alcuni casi di utilizzo per le ellissi quando si lavora con SFINAE per implementare (per esempio) i tratti del tipo, o per la compatibilità C, ma parlerò dei casi d'uso ordinari qui.

modelli variadic, infatti, consentono di tipi differenti per il pacco argomento (infatti, qualsiasi tipo), mentre i valori di una lista di inizializzazione devono essere convertibile tipo sottostante dell'elenco initalizer (e restringimento di conversione sono non consentito):

#include <utility> 

template<typename... Ts> 
void foo(Ts...) { } 

template<typename T> 
void bar(std::initializer_list<T>) { } 

int main() 
{ 
    foo("Hello World!", 3.14, 42); // OK 
    bar({"Hello World!", 3.14, 42}); // ERROR! Cannot deduce T 
} 

a causa di questo, le liste di inizializzazione sono meno spesso utilizzati quando è richiesto il tipo deduzione, a meno che il tipo degli argomenti è infatti destinata ad essere omogenea. I modelli Variadic, d'altra parte, forniscono una versione di tipo sicura da tipo dell'elenco di variabili variabili di ellissi.

Inoltre, il richiamo di una funzione che richiede un elenco di inizializzazione richiede di racchiudere gli argomenti in una coppia di parentesi, che non è il caso di una funzione che accetta un pacchetto di argomenti variadic.

Infine (beh, ci sono altre differenze, ma queste sono più rilevanti per la tua domanda), i valori in una lista di inizializzatori sono oggetti const. Per Paragrafo 18,9/1 della C++ 11 standard:

Un oggetto di tipo initializer_list<E> fornisce accesso ad un array di oggetti di tipo const E. [...] La copia di un elenco di inizializzazione fa sì che non copi gli elementi sottostanti. [...]

Ciò significa che, sebbene i tipi non copiabili possano essere spostati in un elenco di inizializzatori, non possono essere spostati da esso. Questa limitazione può o non può soddisfare i requisiti di un programma, ma generalmente rende gli elenchi di inizializzatori una scelta limitante per contenere tipi non copiabili.

Più in generale, comunque, quando si utilizza un oggetto come elemento di un elenco di inizializzazione, ne faremo una copia (se è un lvalue) o lo spostiamo (se è un valore):

#include <utility> 
#include <iostream> 

struct X 
{ 
    X() { } 
    X(X const &x) { std::cout << "X(const&)" << std::endl; } 
    X(X&&) { std::cout << "X(X&&)" << std::endl; } 
}; 

void foo(std::initializer_list<X> const& l) { } 

int main() 
{ 
    X x, y, z, w; 
    foo({x, y, z, std::move(w)}); // Will print "X(X const&)" three times 
            // and "X(X&&)" once 
} 

In altre parole, elenchi di inizializzazione non può essere utilizzato per passare gli argomenti di riferimento (*), per non parlare eseguendo inoltro perfetto:

template<typename... Ts> 
void bar(Ts&&... args) 
{ 
    std::cout << "bar(Ts&&...)" << std::endl; 
    // Possibly do perfect forwarding here and pass the 
    // arguments to another function... 
} 

int main() 
{ 
    X x, y, z, w; 
    bar(x, y, z, std::move(w)); // Will only print "bar(Ts&&...)" 
} 

(*) Si deve notare, tuttavia, quello initializer lists (unlike all other containers of the C++ Standard Library) do have reference semantics, quindi anche se una copia/spostamento degli elementi viene eseguita quando si inseriscono elementi in un elenco di inizializzazione, la copia dell'elenco di inizializzazione non causerà alcuna copia/spostamento degli oggetti contenuti (come menzionato nel paragrafo della norma citata sopra) :

int main() 
{ 
    X x, y, z, w; 
    auto l1 = {x, y, z, std::move(w)}; // Will print "X(X const&)" three times 
             // and "X(X&&)" once 

    auto l2 = l1; // Will print nothing 
} 
+1

Questa è una risposta fantastica! Molta conoscenza qui, ragazzi. – Gui13

+0

@ Gui13: Grazie, in realtà ci sarebbe stato altro da aggiungere, ma ho cercato di renderlo abbastanza conciso da includere solo i punti che mi sembrano più rilevanti per la domanda che viene posta :) –

+0

Un'altra importante differenza tra i modelli variadici e Varargs in stile C è che una funzione varar in stile C può essere compilata separatamente. Un modello variadico non può. –

1

In sintesi, le funzioni variadic in stile C producono meno codice quando compilato di C++ - modelli di stile variadic, quindi se siete preoccupati per la dimensione del binario o istruzione pressione della cache, si dovrebbe considerare di implementare la funzionalità con varargs invece come un modello.

Tuttavia, i modelli variadic sono significativamente più sicuri e producono messaggi di errore molto più utili, quindi è spesso necessario avvolgere la funzione variadic out-of-line con un modello variadico incorporato e chiedere agli utenti di chiamare il modello.

Problemi correlati