2011-09-01 9 views
37

Ho fatto qualche ricerca dopo l'apprendimento new, a differenza di malloc() a cui sono abituato, non restituisce NULL per allocazioni non riuscite e ho trovato che ci sono due modi distinti di verificare se il nuovo fosse o meno riuscito. Queste due vie sono:Nuovo (std :: nothrow) vs. Nuovo all'interno di un blocco try/catch

try 
{ 
    ptr = new int[1024]; 
} 
catch(std::bad_alloc& exc) 
{ 
    assert(); 
}; 

e

ptr = new (std::nothrow) int[1024]; 
if(ptr == NULL) 
    assert(); 

Credo che i due modi raggiungere lo stesso obiettivo, (! Correggetemi se sbaglio ovviamente), quindi la mia domanda è questa:

che è l'opzione migliore per controllare se new riuscito, basata interamente sulla leggibilità, manutenibilità, e la performance, trascurando c de facto ++ convenzione di programmazione.

+2

Utilizzare le eccezioni. Rendono il tuo codice più significativo, locale, manutenibile e scalabile. –

+3

Per quanto riguarda la leggibilità, sicuramente quella senza gestione delle eccezioni. Per quanto riguarda le prestazioni, forse anche il 'nothrow'. Ma sono sicuro che le eccezionaliste sosterranno. Ma se vuoi solo un semplice errore di asserzione, puoi anche usare la variante di lancio e omettere il gestore delle eccezioni;) –

+4

Qual è il punto di quel codice? Stai rilevando un'eccezione specifica e significativa e poi ne crei una priva di significato tramite 'assert()' ... – Blindy

risposta

47

Considera quello che stai facendo. Stai allocando memoria. E se per qualche motivo l'allocazione della memoria non funziona, è assert. Che è più o meno esattamente ciò che accadrà se lasci che lo std::bad_alloc si propaga nuovamente a main. In una build di rilascio, dove assert è un no-op, il programma si arresta in modo anomalo quando tenta di accedere alla memoria. Quindi è come far scoppiare l'eccezione: fermare l'app.

Quindi, ponetevi una domanda: avete davvero il a preoccuparsi di cosa succede se esaurite la memoria? Se tutto quello che stai facendo è affermare, allora il metodo di eccezione è migliore, perché non ingombra il tuo codice con numeri casuali assert s. Hai appena lasciato che l'eccezione ricada su main.

Se in effetti si dispone di un codepath speciale nel caso in cui non è possibile allocare memoria (ovvero, è effettivamente possibile continuare a funzionare), le eccezioni possono o non possono essere un modo per andare, a seconda di ciò che il codepath è . Se il codepath è solo un interruttore impostato con un puntatore nullo, la versione nothrow sarà più semplice.Se invece, è necessario fare qualcosa di diverso (tirare da un buffer statico, o eliminare alcune cose, o qualsiasi altra cosa), quindi catturare std::bad_alloc è abbastanza buono.

+3

Questo è un argomento piuttosto convincente, non ero a conoscenza che le eccezioni potevano scalare lo stack di chiamate per trovare un blocco catch. ... Non sono a conoscenza di molte cose che le eccezioni possono fare. Potrebbe essere il momento in cui finalmente lo imparerò dopo tutti questi anni, aha –

10

Dipende dal contesto in cui l'assegnazione avviene. Se il tuo programma può continuare anche se l'allocazione fallisce (magari restituire un codice di errore al chiamante), usa il metodo std::nothrow e verifica NULL. Altrimenti stareste usando le eccezioni per il controllo del flusso, che non è una buona pratica.

D'altra parte, se il programma assolutamente bisogno di avere che la memoria allocata con successo al fine di essere in grado di funzionare, utilizzare try-catch per la cattura (non necessariamente nelle immediate vicinanze del new) un'eccezione e uscire con grazia dal programma.

+0

Non l'ho considerato, suppongo che la miscelazione e l'abbinamento per la situazione non sarebbero stati di cattivo gusto in quel caso. Ovviamente i due modi esistono per quella ragione. Grazie! –

+4

Questa è una bella risposta. Esso riassume l'intera filosofia delle eccezioni: usa le eccezioni per situazioni eccezionali e prendile dove possono essere gestite in modo significativo. D'altra parte, per una situazione di runtime non eccezionale che puoi e vuoi gestire localmente, non usare eccezioni. –

6

Da un punto di vista delle prestazioni puro, poco importa. Esiste un sovraccarico inerente con la gestione delle eccezioni, anche se questo overhead è generalmente utile per la leggibilità e la manutenzione delle applicazioni. Errori di allocazione della memoria di questo tipo non dovrebbero essere nel caso del 99% della tua applicazione, quindi questo dovrebbe accadere di rado.

Dal punto di vista delle prestazioni in genere si vuole evitare l'allocatore di serie a causa della sua relativamente scarso rendimento in ogni caso.

Tutto ciò detto, in genere accetto la versione di lancio delle eccezioni perché generalmente le nostre applicazioni si trovano in uno stato in cui, se l'allocazione della memoria fallisce, non possiamo fare altro che uscire con garbo con un messaggio di errore appropriato e salviamo le prestazioni non richiede il controllo di NULL sulle risorse appena allocate perché, per definizione, un errore di allocazione sposterà l'ambito da dove ciò è importante.

+0

Quindi la penalizzazione delle prestazioni delle eccezioni sarebbe inferiore al controllo di uguaglianza dei puntatori, assumendo che nulla vada storto? Se questo è il caso, non vedo alcun motivo per non utilizzare le eccezioni ora. (E sì, provo a racchiudere tutte le mie esigenze di allocazione su un piccolo allocatore di oggetti, mantiene tutte le cose di memoria anche in un posto ordinato) –

+0

C'è ancora qualche penalità quando si usano le eccezioni (il linguaggio fa per te), come ad esempio l'impostazione up the stack appropriatamente, elaborazione SEH, ecc. In generale non mi aspetto che ci sia una differenza misurabile tra i due nel caso in cui "nulla vada storto". In caso di errore, il codice di eccezione sarà molto più coinvolto, ma pulirà il codice non richiedendo i controlli 'NULL'. Comunque è davvero una questione di preferenze personali. – Chad

+2

Nell'implementazione GCC, le eccezioni non vi costano nulla (a parte forse una località di memoria leggermente peggiore) se non vengono utilizzate - pagate solo quando l'eccezione viene effettivamente generata. –

-3

new viene utilizzato per creare oggetti, non allocare memoria, pertanto l'esempio è in qualche modo artificiale.

Generatori di oggetti in genere lanciano se falliscono. Avendo superato l'implementazione new in Visual Studio più volte, non credo che il codice rilevi eccezioni. Pertanto, in genere è opportuno cercare eccezioni durante la creazione di oggetti.

I thinkstd::bad_alloc viene generato solo se la parte di allocazione di memoria non riesce. Non sono sicuro di cosa succederà se passi std::nothrow a new ma il costruttore dell'oggetto getta - c'è un'ambiguità nei documenti che ho letto.

La differenza di prestazioni tra i 2 approcci è probabilmente irrilevante poiché la maggior parte del tempo del processore può essere facilmente spesa nel costruttore di oggetti o nella ricerca nell'heap.

Una regola empirica non è sempre appropriata. Ad esempio, i sistemi in tempo reale limitano in genere le allocazioni di memoria dinamica, quindi new, se presente, verrebbe probabilmente sovraccaricato. In tal caso, potrebbe fare uso di un puntatore nullo restituito e gestire l'errore localmente.

+0

Se il tuo oggetto principale ha dei sottooggetti, questi possono anche lanciare bad_alloc se allocano memoria ... quindi non saprai con certezza se è l'allocazione principale che ha fallito. Tuttavia, non ti interessa perché l'oggetto non è comunque utilizzabile e il compilatore si occuperà di eseguire la pulizia necessaria degli oggetti parzialmente costruiti. ** Ti suggerisco di imparare la lingua prima ** prima di dare risposte arbitrarie che sono fuorvianti. In particolare, la tua raccomandazione di cercare eccezioni durante la creazione di oggetti è errata se si intende mettere alla prova/catturare ogni allocazione. – Phil1970

+0

Vorrei raccomandarti di leggere ** C++ eccezionale e altri libri simili prima di scrivere qualsiasi codice utilizzato nelle applicazioni professionali. – Phil1970

Problemi correlati