2012-02-21 19 views
52

So che at() è più lento di [] a causa del suo controllo di confine, che è anche discusso in questioni simili, come C++ Vector at/[] operator speed o ::std::vector::at() vs operator[] << surprising results!! 5 to 10 times slower/faster!. Semplicemente non capisco per cosa sia utile il metodo at().vector :: al vs. vector :: operator []

Se ho un semplice vettore come questo: std::vector<int> v(10); e decido di accedere ai suoi elementi utilizzando at() invece di [] nella situazione in cui ho un indice di i e non sono sicuro se i suoi limiti in vettori, forza mi avvolgerlo con blocco try-catch:

try 
{ 
    v.at(i) = 2; 
} 
catch (std::out_of_range& oor) 
{ 
    ... 
} 

anche se sono in grado di fare lo ottenere lo stesso comportamento utilizzando size() e controllando l'indice per conto mio, che sembra più facile e molto conveniente per me :

if (i < v.size()) 
    v[i] = 2; 

Quindi la mia domanda è:
Quali sono i vantaggi di utilizzare vector::at sopra vector::operator[]?
Quando è necessario utilizzare vector::at anziché vector::size + vector::operator[]?

+6

+1 molto buona domanda !! ma non penso che sia comunemente usato. –

+9

Nota che nel tuo codice di esempio, 'if (i = v.size()'. Quindi non c'è una ragione particolare per cui * non dovrebbe * usare un'eccezione per indicare una situazione inaspettata. Molte funzioni usano semplicemente 'operator []' senza un controllo rispetto alla dimensione, documentano che 'i' deve essere nel raggio d'azione e incolpano l'UB risultante sul chiamante. –

risposta

37

direi che le eccezioni che vector::at() tiri non sono realmente destinati ad essere catturato dal codice immediatamente circostante. Sono principalmente utili per catturare bug nel tuo codice. Se è necessario il controllo dei limiti in fase di esecuzione, ad es. l'indice proviene dall'input dell'utente, è davvero meglio con una dichiarazione if. Quindi, in sintesi, progetta il tuo codice con l'intenzione che vector::at() non genererà mai un'eccezione, così che se lo fa, e il tuo programma si interrompe, è un segno di un bug. (proprio come un assert())

+0

+1 Mi piace la spiegazione di come separare la gestione dell'input dell'utente sbagliato (convalida dell'input, potrebbe essere previsto un input non valido quindi non è considerato come qualcosa di eccezionale) ... e bug nel codice (l'iteratore di dereferenziamento fuori range è cosa eccezionale) –

+0

Quindi tu dici che dovrei usare 'size()' + '[]' quando l'indice dipende dall'input dell'utente, usa 'assert' in situazioni in cui index non dovrebbe mai essere fuori limite per una facile risoluzione dei bug in futuro e '.at()' in tutte le altre situazioni (nel caso in cui, potrebbe accadere qualcosa di sbagliato ...) – LihO

+7

@LihO: se la tua implementazione offre un'implementazione di debug di 'vector', probabilmente è meglio usarla come" solo " nel caso "opzione piuttosto che' a() 'ovunque. In questo modo puoi sperare di ottenere un po 'più di prestazioni in modalità di rilascio, nel caso tu ne abbia mai bisogno. –

12

mi costringe a avvolgerlo con blocco try-catch

No, non (blocco try/catch può essere a monte). È utile quando vuoi che venga lanciata un'eccezione piuttosto che il tuo programma per entrare nel regno del comportamento non definito.

Sono d'accordo che la maggior parte fuori dai limiti accessi ai vettori sono l'errore di un programmatore (nel qual caso si dovrebbe usare assert per individuare quegli errori più facilmente; la maggior parte delle versioni di debug delle librerie standard di farlo automaticamente per voi). Non si desidera utilizzare eccezioni che possono essere ingerite a monte per segnalare errori del programmatore: si desidera essere in grado di correggere l'errore .

Poiché è improbabile che un fuori limite l'accesso a un vettore faccia parte del normale flusso del programma (nel caso lo sia, hai ragione: controlla prima con size invece di far scoppiare l'eccezione), I d'accordo con la tua diagnosi: at è sostanzialmente inutile.

+0

Se non rilevo l'eccezione 'out_of_range', viene chiamato' abort() '. – LihO

+0

@LihO: Non necessariamente ... il 'try..catch' può essere presente nel metodo che chiama questo metodo. – Naveen

+9

Se non altro, 'at' è utile nella misura in cui altrimenti ti ritroverai a scrivere qualcosa come' if (i

8

Quali sono i vantaggi dell'utilizzo di vector :: at vector :: operator []? Quando dovrei usare vector :: at piuttosto che vector :: size + vector :: operator []?

Il punto importante è che le eccezioni consentono separazione del normale flusso di codice dalla logica gestione degli errori, e un singolo blocco catch in grado di gestire i problemi derivanti da uno qualsiasi dei siti throw miriade, anche se dispersa in profondità all'interno chiamate di funzione . Quindi, non è che at() sia necessariamente più semplice per un singolo uso, ma che a volte diventi più semplice - e meno offuscante della logica del caso normale - quando hai un sacco di indicizzazione da convalidare.

È anche degno di nota il fatto che in alcuni tipi di codice, un indice viene incrementato in modi complessi e continuamente utilizzato per cercare una matrice. In questi casi, è molto più semplice garantire i controlli corretti utilizzando at().

Come esempio del mondo reale, ho un codice che converte il C++ in elementi lessicali, quindi un altro codice che sposta un indice sul vettore di token. A seconda di ciò che è incontrato, mi può voler incrementare e controllare l'elemento successivo, come in:

if (token.at(i) == Token::Keyword_Enum) 
{ 
    ASSERT_EQ(tokens.at(++i), Token::Idn); 
    if (tokens.at(++i) == Left_Brace) 
     ... 
    or whatever 

In questo tipo di situazione, è molto difficile da controllare se hai impropriamente raggiunto la fine dell'input perché questo dipende molto dai token esatti incontrati. Il controllo esplicito in ogni punto di utilizzo è doloroso e c'è molto più spazio per errori del programmatore come incrementi pre/post, offset nel punto di utilizzo, ragionamento imperfetto sulla validità di alcuni test precedenti ecc. Kicking.

1

L'intero punto di utilizzo delle eccezioni è che il codice di gestione degli errori può essere più lontano.

In questo caso specifico, l'input dell'utente è davvero un buon esempio. Immagina di voler analizzare semanticamente una struttura dati XML che utilizza gli indici per fare riferimento a qualche tipo di risorsa che internamente memorizzi in un std::vector. Ora l'albero XML è un albero, quindi probabilmente vorrai usare la ricorsione per analizzarlo. In fondo, nella ricorsione, potrebbe esserci una violazione di accesso da parte del writer del file XML. In tal caso, di solito vuoi uscire da tutti i livelli di ricorsione e solo rifiutare l'intero file (o qualsiasi tipo di struttura "grossolana"). Questo è dove è utile. Puoi semplicemente scrivere il codice di analisi come se il file fosse valido. Il codice della libreria si prenderà cura del rilevamento degli errori e puoi semplicemente rilevare l'errore a livello di massima.

Inoltre, altri contenitori, come std::map, hanno anche std::map::at che contiene semantica leggermente diverse rispetto std::map::operator[]: a può essere utilizzato su una mappa const, mentre operator[] non può. Ora, se si desidera scrivere codice agnostico del contenitore, come qualcosa che potrebbe riguardare sia const std::vector<T>& o const std::map<std::size_t, T>&, ContainerType::at sarebbe la vostra arma preferita.

Tuttavia, tutti questi casi vengono visualizzati in genere quando si gestisce un tipo di input dati non convalidato. Se sei sicuro del tuo intervallo valido, come di solito dovresti essere, di solito puoi usare operator[], ma meglio ancora, iteratori con begin() e end().

5

Innanzitutto, se at() o operator[] non è specificato. Quando non ci sono errori, mi aspetto che abbiano circa la stessa velocità, a almeno nei build di debug. La differenza è che at() specifica esattamente ciò che accadrà in presenza di un errore limiti (un'eccezione), dove, come nel caso di operator[], è un comportamento indefinito — un incidente in tutti i sistemi che uso (g ++ e VC++), almeno quando vengono utilizzati i flag di debugging standard .(Un'altra differenza è che una volta che sono sicuro del mio codice , posso ottenere un sostanziale aumento di velocità per operator[] disattivando il debug. Se le prestazioni lo richiedono, lo — I non lo farebbe se non fosse necessario.)

In pratica, at() è raramente appropriato. Se il contesto è tale che sai che l'indice potrebbe essere non valido, probabilmente vuoi il test esplicito (ad esempio per restituire un valore predefinito o qualcosa del genere), e se sai che è non può essere non valido, vuoi abortire (e se non si sa se sia non valido o meno, suggerirei di specificare l'interfaccia della propria funzione in modo più preciso). Ci sono alcune eccezioni, tuttavia, dove l'indice non valido può risultare dall'analisi dei dati utente e l'errore dovrebbe causare l'interruzione dell'intera richiesta (ma non portare il server in basso); in questi casi, è necessaria un'eccezione e at() eseguirà per te .

+2

Perché dovresti aspettarti che abbiano circa la stessa velocità, quando 'operator []' non è costretto a limiti- controllare, mentre 'a()' è? Sei per quello che implica problemi di memorizzazione nella cache, di speculazione e di ramificazione? –

+0

@phresnel 'operator []' non è richiesto per eseguire il controllo dei limiti, ma tutte le migliori implementazioni lo fanno. Almeno in modalità di debug. L'unica differenza è che cosa fanno se l'indice è fuori limite: 'operator []' si interrompe con un messaggio di errore, 'at()' lancia un'eccezione. –

+2

Mi dispiace, mi sono perso il tuo "in debugging mode" -attributo. Tuttavia, non vorrei misurare il codice sulla sua qualità in modalità di debug. Nella modalità di rilascio, il controllo è richiesto solo da 'at()'. –

0

Secondo l'articolo this, prestazioni a parte, non fa alcuna differenza usare at o operator[], solo se l'accesso è garantito all'interno della dimensione del vettore. Altrimenti, se l'accesso è basato solo sulla capacità del vettore, è più sicuro usare at.

+0

là fuori ci sono i draghi. cosa succede se clicchiamo su quel link? (suggerimento: lo so già, ma su StackOverflow preferiamo i commenti che non soffrono di link rot, cioè fornisci un breve riassunto su cosa vuoi dire) –

+0

Grazie per il suggerimento. È ora riparato. – ahj

4

at può essere più chiaro se si dispone di un puntatore al vettore:

return pVector->at(n); 
return (*pVector)[n]; 
return pVector->operator[](n); 

Prestazioni a parte, il primo di questi è il codice più semplice e più chiaro.

+0

...specialmente quando hai bisogno di un puntatore all'elemento _n_-th di un vettore. – dolphin

-1

Qui ci sono molte risposte sbagliate. C'è davvero una sola differenza: at non controlla i limiti mentre operator[] no. Questo si applica alle build di debug e alle build di rilascio e questo è molto ben specificato dagli standard. È così semplice.

Questo rende at un metodo più lento ma è anche un consiglio davvero pessimo per non utilizzare at. Devi guardare numeri assoluti, non numeri relativi tra due. Il più delle volte, ci sono operazioni molto più costose di at. Personalmente, io uso sempre at perché non voglio un bug sgradevole per creare comportamenti indefiniti e intrufolarsi alla produzione. Io uso solo operator[] se ho la prova che è una causa significativa di qualche problema di prestazioni che mi dà fastidio.

+0

Non sono sicuro del motivo per cui le persone si avvicinano senza ragionamento. La risposta sopra è corretta e può essere verificata con riferimento C++. – ShitalShah