Come altri hanno sottolineato, assert
è una specie del tuo ultimo bastione di difesa contro errori del programmatore che non dovrebbe mai accadere. Si tratta di controlli di sanità mentale che si spera che non manchino a destra ea sinistra al momento della spedizione.
È inoltre progettato per essere omesso da build di versioni stabili, per qualsiasi motivo gli sviluppatori possano trovare utili: estetica, prestazioni, qualunque cosa vogliano. Fa parte di ciò che separa una build di debug da una build di rilascio e per definizione una build di rilascio è priva di tali asserzioni. Quindi c'è una sovversione del progetto se si desidera rilasciare la "build di rilascio analogica con asserzioni in atto" che sarebbe un tentativo di una build di rilascio con una _DEBUG
definizione del preprocessore e non NDEBUG
definita; non è più una build di rilascio.
Il design si estende anche nella libreria standard. Come esempio di base tra numerosi, molte implementazioni di std::vector::operator[]
volontà saranno assert
un controllo di integrità per assicurarsi che non si stia verificando il vettore fuori dai limiti. E la libreria standard inizierà a funzionare molto, molto peggio se si abilitano tali controlli in una build di rilascio. Un punto di riferimento vector
dell'utilizzooperator[]
e un ctor di riempimento con tali asserzioni incluse contro un semplice array dinamico vecchio mostrerà spesso che l'array dinamico è considerevolmente più veloce fino a quando non disabiliti tali controlli, quindi spesso influiscono sulle prestazioni in modi lontani, tutt'altro che banali. Un controllo puntatore nullo qui e un controllo fuori limite lì possono effettivamente diventare una spesa enorme se tali controlli vengono applicati milioni di volte su ogni frame in loop critici che precedono il codice semplice come il dereferenziamento di un puntatore intelligente o l'accesso a un array.
Quindi probabilmente stai desiderando uno strumento diverso per il lavoro e uno che non è progettato per essere omesso dai build di rilascio se vuoi build di rilascio che eseguono tali controlli di integrità in aree chiave. Il più utile che trovo personalmente è la registrazione. In tal caso, quando un utente segnala un bug, le cose diventano molto più semplici se allegano un registro e l'ultima riga del registro mi dà un indizio grande su dove si è verificato il bug e su quale potrebbe essere. Quindi, riproducendo i loro passi in una build di debug, potrei anche ottenere un errore di asserzione, e quel fallimento di asserzione mi dà ulteriori indizi per semplificare il mio tempo. Tuttavia, poiché la registrazione è relativamente costosa, non la utilizzo per applicare controlli di integrità di livello estremamente basso, come assicurarsi che a una matrice non sia possibile accedere senza limiti in una struttura di dati generica.
Eppure alla fine, e un po 'd'accordo con te, ho potuto vedere un caso ragionevole in cui potresti voler consegnare ai tester qualcosa che assomiglia a una build di debug durante i test alfa, ad esempio con un piccolo gruppo di tester alfa che, diciamo, hanno firmato un accordo di non divulgazione . Lì potrebbe semplificare il test alfa se dai ai tuoi tester qualcosa di diverso da una build a rilascio completo con alcune informazioni di debug allegate insieme ad alcune funzionalità di debug / sviluppo come test che possono eseguire e output più dettagliato mentre eseguono il software. Ho visto almeno alcune grandi società di giochi fare cose del genere per Alpha. Ma è per qualcosa come alfa o test interni in cui stai davvero cercando di dare ai tester qualcosa di diverso da una build di rilascio. Se stai effettivamente cercando di distribuire una build di rilascio, quindi per definizione, non dovrebbe avere_DEBUG
definito altrimenti confonde davvero la differenza tra una build "debug" e "release".
Perché questo codice deve essere rimosso prima del rilascio? I controlli non riducono molto le prestazioni e se falliscono c'è sicuramente un problema di cui preferirei un messaggio di errore più diretto.
Come indicato sopra, i controlli non sono necessariamente banali dal punto di vista delle prestazioni. Molti sono probabilmente banali ma, di nuovo, anche la lib standard li usa e potrebbe influire sulle prestazioni in modi inaccettabili per molte persone in molti casi se, diciamo, l'attraversamento ad accesso casuale std::vector
richiedesse 4 volte di più in quella che dovrebbe essere una build di rilascio ottimizzata a causa del controllo dei suoi limiti che non dovrebbe mai fallire.
In un ex team dovevamo effettivamente fare in modo che la nostra libreria di matrici e vettori escludesse alcuni asserzioni in alcuni percorsi critici solo per rendere più veloci le build di debug, perché quegli asserzioni rallentavano le operazioni matematiche di un ordine di grandezza fino al punto in cui si trovava iniziando a chiederci di aspettare 15 minuti prima che potessimo anche risalire al codice di interesse. I miei colleghi in realtà volevano solo rimuovere il fileasserts
perché hanno scoperto che il solo fatto di fare una differenza enorme. Invece, abbiamo deciso di evitare che i percorsi di debug critici li evitassero. Quando abbiamo fatto in modo che quei percorsi critici usassero direttamente i dati vettore / matrice senza passare attraverso il controllo dei limiti, il tempo richiesto per eseguire l'operazione completa (che includeva più della semplice matematica vettoriale / matrice) si riduceva da minuti a secondi. Quindi questo è un caso estremo ma sicuramente le affermazioni non sono sempre trascurabili dal punto di vista delle prestazioni, nemmeno vicine.
Ma è anche il modo in cui asserts
sono progettati. Se non avessero avuto un così grande impatto sulle prestazioni su tutta la linea, allora potrei favorirlo se fossero progettati come qualcosa di più di una funzione di build di debug o potremmo usare vector::at
che include i limiti che controllano anche nelle build di rilascio e si lancia fuori dai limiti accesso, ad es. (ma con un enorme successo di prestazioni) Ma attualmente trovo il loro design molto più utile, dato il loro enorme impatto sulle prestazioni nei miei casi, come una funzione di solo debug che viene omessa quando NDEBUG
definita. Almeno per i casi con cui ho lavorato, fa una grande differenza per una build di rilascio escludere i controlli di integrità che non dovrebbero mai fallire in primo luogo.
vector::at
vs. vector::operator[]
Penso che la distinzione di questi due metodi sia al centro di questo e dell'alternativa: le eccezioni. vector::operator[]
implementazioni in genere assert
per assicurarsi che l'accesso fuori dai limiti inneschi un errore facilmente riproducibile quando si tenta di accedere a un vettore fuori limite. Ma gli implementatori della libreria lo fanno supponendo che non costerà un centesimo in una build di rilascio ottimizzata.
Nel frattempo vector::at
viene fornito che esegue sempre il controllo dei limiti e genera anche nelle build di rilascio, ma ha una penalità di prestazione al punto in cui vedo spesso molto più codice che usare vector::operator[]
rispetto a vector::at
. Gran parte del design del C ++ fa eco all'idea di "pagare per quello che usi / hai bisogno", e molte persone spesso favoriscono operator[]
, il che non si preoccupa nemmeno dei limiti che controllano nelle build di rilascio, in base all'idea che non indossano non è necessario che i limiti vengano controllati nelle build di rilascio ottimizzate. Improvvisamente se le affermazioni fossero abilitate nelle build di rilascio, le prestazioni di questi due sarebbero identiche e l'uso del vettore finirebbe sempre per essere più lento di un array dinamico. Quindi una parte enorme del design e dei vantaggi delle asserzioni si basa sull'idea che queste diventano libere in una build di rilascio.
release_assert
Questo è interessante dopo aver scoperto queste intenzioni. Naturalmente i casi d'uso di ognuno sarebbero diversi, ma penso che troverei un uso per uno release_assert
che fa il controllo e bloccherà il software mostrando un numero di riga e un messaggio di errore anche nelle build di rilascio.
Sarebbe per alcuni casi oscuri nel mio caso in cui non voglio che il software si ripristini con grazia come farebbe se viene generata un'eccezione. Vorrei che si arrestasse in modo anomalo anche in rilascio in quei casi in modo che all'utente potesse essere assegnato un numero di riga per segnalare quando il software ha riscontrato qualcosa che non dovrebbe mai accadere, ancora nel regno dei controlli di integrità per errori del programmatore, non errori di input esterni come eccezioni, ma abbastanza economico da fare senza preoccuparsi del suo costo di rilascio.
In realtà ci sono alcuni casi in cui troverei un arresto anomalo con un numero di riga e un messaggio di errore preferibile al recupero con grazia da un'eccezione generata che potrebbe essere abbastanza economica da mantenere in una versione. E ci sono alcuni casi in cui è impossibile recuperare da un'eccezione, come un errore riscontrato nel tentativo di recuperare da uno esistente. Lì troverei una misura perfetta per un release_assert(!"This should never, ever happen! The software failed to fail!");
e naturalmente sarebbe sporco a buon mercato poiché il controllo sarebbe eseguito all'interno di un percorso eccezionale in primo luogo e non costerebbe nulla nei normali percorsi di esecuzione.