O è sicuro usare il vettore se l'Enumeratore di T sta solo elencando tutti gli elementi?Esiste un equivalente C++ standard di IEnumerable <T> in C#?
risposta
Non è necessaria in C++ , ed ecco perché:
C# supporta solo pol dinamico ymorphism. Quindi, per creare un algoritmo riutilizzabile, hai bisogno di un'interfaccia che tutti gli iteratori implementeranno. Quello è IEnumerator<T>
e IEnumerable<T>
è una factory per la restituzione di un iteratore.
I modelli C++, d'altra parte, supportano la digitazione anatra. Ciò significa che non è necessario limitare un parametro di tipo generico da un'interfaccia per accedere ai membri: il compilatore cercherà i membri per nome per ogni singola istanza del modello.
I contenitori e gli iteratori C++ hanno interfacce implicite equivalenti a.NET IEnumerable<T>
, IEnumerator<T>
, ICollection<T>
, IList<T>
, vale a dire:
per contenitori:
iterator
econst_iterator
typedef- funzione
begin()
membro - riempie la necessità di funzioneIEnumerable<T>::GetEnumerator()
end()
membro - invece diIEnumerator<T>::MoveNext()
valore restituito
Per iteratori forward:
value_type
typedefoperator++
- anzichéIEnumerator<T>::MoveNext()
operator*
eoperator->
- invece diIEnumerator<T>::Current
- tipo di ritorno di riferimento da
operator*
- anzichéIList<T>
indicizzatore setter operator==
eoperator!=
- nessun vero equivalente in .NET, ma conend()
le partite del contenitoreIEnumerator<T>::MoveNext()
valore di ritorno
Per iteratori ad accesso casuale:
operator+
,operator-
,operator[]
- invece diIList<T>
Se si definiscono questi, gli algoritmi standard funzioneranno con il contenitore e l'iteratore. Nessuna interfaccia è necessaria, non sono necessarie funzioni virtuali. Non utilizzare le funzioni virtuali rende il codice generico C++ più veloce di un codice .NET equivalente, a volte molto più veloce.
Nota: durante la scrittura di algoritmi generici, è meglio usare std::begin(container)
e std::end(container)
invece delle funzioni membro contenitore. Ciò consente al tuo algoritmo di essere utilizzato con matrici raw (che non hanno funzioni membro) oltre ai contenitori STL. Array grezzi e puntatori grezzi soddisfano tutti gli altri requisiti di contenitori e iteratori, con questa unica eccezione.
Questa è una buona spiegazione per il consumo di equivalenti IEnumerable ma per quanto riguarda la loro produzione? Cosa succede se voglio definire un'interfaccia che esponga un membro che posso fare begin() e end() sopra ma senza preoccuparsi del tipo specifico che implementa quel membro? – Sander
Andrei Alexandrescu non è d'accordo con te. Vedi "Iterators must go": http://zao.se/~zao/boostcon/09/2009_presentations/wed/iterators-must-go.pdf Gli intervalli C++/D sono la sostituzione suggerita e non sai, gli intervalli corrispondono quasi esattamente all'interfaccia IEnumerator .NET. – naasking
@naasking: non so come ciò costituisca "disaccordo". Ora abbiamo due modi per iterare le gamme senza un'interfaccia virtualmente inviata. La tua affermazione che gli intervalli sono 'IEnumerator' mostra l'ignoranza di cosa sia effettivamente' IEnumerator'. Gli intervalli sono tipi di anatra, .NET 'IEnumerable' viene inviato dinamicamente. E per favore nota che le gamme, per tutti i loro vantaggi, non sono ancora il modo canonico di fare le cose in Standard C++. –
IEnumerable<T>
è concettualmente molto diverso da vector
.
Il IEnumerable
fornisce inoltro solo, sola lettura accesso ad una sequenza di oggetti, indipendentemente da ciò che il contenitore (se presente) contiene gli oggetti. A vector
è in realtà un contenitore stesso.
In C++, se si desidera fornire l'accesso a un contenitore senza fornire i dettagli di questo contenitore, la convenzione deve passare in due iteratori che rappresentano l'inizio e la fine del contenitore.
Un buon esempio è il C++ definizione STL di accumulate, che può essere in contrasto con IEnumerable<T>.Aggregate
In C++
int GetProduct(const vector<int>& v)
{
// We don't provide the container, but two iterators
return std::accumulate(v.begin(), v.end(), 1, multiplies<int>());
}
In C#
int GetProduct(IEnumerable<int> v)
{
v.Aggregate(1, (l, r) => l*r);
}
Questa risposta mostra solo il lato consumante e non è di grande aiuto. – BrainSlugs83
@ BrainSlugs83: i tuoi commenti e downvotes non sono di grande aiuto. Nulla nella domanda si interroga sullo zucchero sintattico per l'implementazione dell'iteratore usando le coroutine, che è esattamente ciò che C# 'restituisce. Questa domanda non enfatizza né il lato dell'implementazione né il lato del consumo, si interroga sull'interfaccia iteratore e non sui wrapper di convenienza. Stai imponendo la tua curiosità sull'implementazione, che è naturale, ma non utile. Se vuoi conoscere i dettagli di implementazione, poni la tua domanda, non dirottarla. –
Lo standard C++ modo è quello di superare due iteratori:
template<typename ForwardIterator>
void some_function(ForwardIterator begin, ForwardIterator end)
{
for (; begin != end; ++begin)
{
do_something_with(*begin);
}
}
Esempio codice client:
std::vector<int> vec = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(vec.begin(), vec.end());
std::list<int> lst = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(lst.begin(), lst.end());
int arr[] = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(arr + 0, arr + 8);
Yay programmazione generica!
il modo più generico è usare le funzioni libere 'std :: begin' e' std :: end' – Abyx
@Abyx: il codice client funziona su tipi non dipendenti che hanno in modo dimostrabile '.begin' e' .end' così non fa nessuna differenza qui. In ogni caso, la genericità di 'some_function' è ciò che è importante e quella scelta nel codice client non influisce sulla sua implementazione. –
@Abyx: codice cliente generico? :) – fredoverflow
Se ci atteniamo rigorosamente alla domanda, la risposta non è per quanto ne so. Le persone hanno continuato a rispondere a ciò che è il sostituto disponibile in C++, che potrebbe essere una buona informazione ma non una risposta, e che Derekhh probabilmente conosceva già.
Sono completamente in disaccordo sul fatto che "non è necessario", è solo che i progetti delle librerie standard C++ e .NET sono diversi.La caratteristica principale di IEnumerable <> è che è polimorfica e consente al chiamante di utilizzare qualsiasi classe desideri (array, elenco, set ecc.), Pur fornendo una digitazione potente in fase di compilazione, anche in caso di errore di sicurezza .
L'unica alternativa in C++ è modelli. Ma i modelli C++ non sono generici di run time generati in modo sicuro, sono fondamentalmente dei tipi di macro. Quindi, prima di tutto con i modelli in C++, sei obbligato a fornire l'intero codice sorgente del template a chiunque abbia bisogno di usare il tuo modello. Inoltre se rendi la tua libreria api templata perdi la capacità di garantire che una chiamata venga compilata e il codice non sia automaticamente auto-documentante.
Condivido pienamente con qualsiasi altro programmatore che utilizza C# e C++ ed è frustrato con questo punto. Secondo la mia opinione, gli iteratori sono design inferiore rispetto a idee più moderne. Le prestazioni non sono certo un argomento, se lo voglio potrei sempre usare i puntatori grezzi; specialmente dal momento che gli iteratori sono poco più di puntatori sottili. Prova a de-referenziare un iteratore che avviene in fase di esecuzione a == .end() perché ti sei dimenticato di controllare, e riceverai un riferimento non valido - anche se l'intera idea di design dietro C++ che introduceva in modo ridondante riferimenti oltre ai puntatori C era che il primo era presumibilmente garantito per non essere nullo o invalido.
"I modelli C++ non sono generati in modo sicuro generatori di runtime" è incredibilmente fuorviante. Sono genericamente digitati (molto più in sicurezza rispetto al polimorfismo .NET) in fase di compilazione (che consente l'ottimizzazione) generici. –
Lasciatemi riformulare: i modelli C++ sono solo macro. C++ è ovviamente fortemente sicuro per il tipo - come C#. –
I modelli C++ sono sostanzialmente diversi nel comportamento dalle macro, sia dalla varietà debole di macro C sia da qualsiasi macro-processore super-potente indipendente dalla lingua che si desidera adottare. I modelli e il sistema dei tipi sono profondamente intrecciati. –
C'è un equivalente che potresti usare in C++, ti piacerebbe vedere un esempio di codice – MethodMan
@DJKRAZE: Grazie! Sto solo cercando di vedere se ci sono approcci più appropriati diversi dall'uso di vettori, che potrebbero consentire le nostre implementazioni di GetEnumerator() anche in C++. – derekhh