Perché i messaggi di errore del modello C ++ sono così orribili?

28

I modelli C ++ sono noti per generare messaggi di errore lunghi e illeggibili. Ho un'idea generale del perché i messaggi di errore del modello in C ++ siano così negativi. In sostanza, il problema è che l'errore non viene attivato fino a quando il compilatore non incontra la sintassi che non è supportata da un determinato tipo in un modello. Ad esempio:

template <class T>
void dosomething(T& x) { x += 5; }

Se T non supporta l'operatore += , il compilatore genererà un messaggio di errore. E se questo accade in profondità in una libreria da qualche parte, il messaggio di errore potrebbe essere lungo migliaia di righe.

Ma i modelli C ++ sono essenzialmente solo un meccanismo per la digitazione anatra in fase di compilazione. Un errore di modello C ++ è concettualmente molto simile a un errore di tipo runtime che potrebbe verificarsi in un linguaggio dinamico come Python. Ad esempio, considera il seguente codice Python:

def dosomething(x):
   x.foo()

Qui, se x non ha un metodo foo() , l'interprete Python genera un'eccezione e visualizza una traccia dello stack insieme a un messaggio di errore piuttosto chiaro che indica il problema. Anche se l'errore non viene attivato fino a quando l'interprete si trova in profondità all'interno di alcune funzioni della libreria, il messaggio di errore di runtime non è comunque così male come il vomito illeggibile emesso da un tipico compilatore C ++. Quindi, perché un compilatore C ++ non può essere più chiaro su cosa è andato storto? Perché alcuni messaggi di errore del modello C ++ provocano letteralmente lo scorrimento della finestra della console per oltre 5 secondi?

    
posta Channel72 21.04.2011 - 01:57
fonte

3 risposte

27

I messaggi di errore del modello possono essere noti, ma non sono sempre lunghi e illeggibili. In questo caso, l'intero messaggio di errore (da gcc) è:

test.cpp: In function ‘void dosomething(T&) [with T = X]’:
test.cpp:11:   instantiated from here
test.cpp:6: error: no match for ‘operator+=’ in ‘x += 5’

Come nel tuo esempio Python, ottieni una "pila traccia" di punti di istanziazione del modello e un chiaro messaggio di errore che indica il problema.

A volte i messaggi di errore relativi ai modelli possono essere molto più lunghi, per vari motivi:

  • La "traccia dello stack" potrebbe essere molto più profonda
  • I nomi dei tipi potrebbero essere molto più lunghi, poiché i modelli vengono istanziati con altre istanze dei modelli come loro argomenti e visualizzati con tutti i loro qualificatori di spazio dei nomi
  • Quando la risoluzione del sovraccarico non riesce, il messaggio di errore potrebbe contenere un elenco di sovraccarichi candidati (che potrebbero contenere ciascuno dei nomi di caratteri molto lunghi)
  • Lo stesso errore può essere segnalato molte volte, se un modello non valido viene istanziato in molti punti

La principale differenza da Python è il sistema di tipo statico, che porta alla necessità di includere i nomi di tipo (a volte lunghi) nel messaggio di errore. Senza di essi, a volte sarebbe molto difficile diagnosticare perché la risoluzione di sovraccarico non è riuscita. Con loro, la tua sfida non è più quella di indovinare dove si trova il problema, ma di decifrare i geroglifici che ti dicono dove si trova.

Inoltre, il controllo in fase di esecuzione indica che il programma si arresterà al primo errore che incontra, visualizzando solo un singolo messaggio. Un compilatore potrebbe visualizzare tutti gli errori che incontra, fino a quando non si arrende; almeno in C ++, non dovrebbe fermarsi sul primo errore nel file, poiché ciò potrebbe essere una conseguenza di un errore successivo.

    
risposta data 21.04.2011 - 03:04
fonte
12

Alcune delle ovvie ragioni includono:

  1. Storia. Quando gcc, MSVC, ecc. Erano nuovi, non potevano permettersi di utilizzare molto spazio extra per archiviare i dati per produrre messaggi di errore migliori. La memoria era abbastanza scarsa da non poterli fare.
  2. Per anni, i consumatori ignoravano la qualità dei messaggi di errore, quindi anche i fornitori lo facevano per lo più.
  3. Con un po 'di codice, il compilatore può risincronizzare e diagnosticare errori reali più avanti nel codice. Gli errori nei template si sovrappongono così tanto che qualsiasi cosa dopo la prima è quasi sempre inutile.
  4. La flessibilità generale dei modelli rende difficile indovinare cosa intendi probabilmente quando il tuo codice ha un errore.
  5. All'interno di un modello, il significato di un nome dipende sia dal contesto del modello, sia dal contesto dell'istanziazione, la ricerca dipendente da argomenti e può aggiungere ulteriori possibilità.
  6. L'overloading delle funzioni può fornire lotti di candidati per ciò a cui una particolare funzione chiamata potrebbe fare riferimento, e alcuni compilatori (ad es. gcc) li elencano doverosamente quando c'è un'ambiguità .
  7. Molti codificatori che non considererebbero mai l'utilizzo di parametri normali senza garantire che i valori passati soddisfino i requisiti, non tentano nemmeno di controllare i parametri del modello (e devo confessarlo, tendo verso questo io stesso).

Questo è tutt'altro che esaustivo, ma ottieni l'idea generale. Anche se non è facile, la maggior parte può essere curata. Per anni, stavo dicendo alla gente di avere una copia di Comeau C ++ per un uso regolare; Probabilmente ho risparmiato abbastanza da un messaggio di errore una volta per pagare il compilatore. Ora Clang sta arrivando allo stesso punto (ed è anche meno costoso).

Chiuderò con un'osservazione generale che sembra uno scherzo, ma in realtà non lo è. Il più delle volte, il vero lavoro di un compilatore è per trasformare il codice sorgente in messaggi di errore. È tempo che i venditori si concentrino per fare quel lavoro un po 'meglio, anche se ammetto apertamente che quando ho scritto i compilatori, ho avuto una strong tendenza a considerarlo secondario (nella migliore delle ipotesi) e in alcuni casi quasi ignorato completamente.

    
risposta data 21.04.2011 - 06:26
fonte
9

La risposta è semplice, perché Python è stato progettato per funzionare in questo modo, mentre molte delle cose associate ai template sono state scoperte per caso. Ad esempio, non è mai stato concepito per diventare un sistema completo di Turing. E se non riesci a pianificare e ragionare deliberatamente su cosa succede quando il tuo sistema funziona , perché qualcuno dovrebbe aspettarsi una pianificazione attenta e ponderata su cosa succede quando qualcosa va storto?

Inoltre, come hai sottolineato, l'interprete Python può renderlo molto più semplice con te visualizzando una traccia stack perché interpreta il codice Python. Se un compilatore C ++ riscontra un errore di modello e ti dà una traccia di stack, sarebbe altrettanto inutile di "vomitare modello", vero?

    
risposta data 21.04.2011 - 02:11
fonte

Leggi altre domande sui tag