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 ++ sono così negativi. In sostanza, il problema è che l'errore non viene attivato fino a quando il compilatore non incontra una sintassi non supportata da un certo tipo in un modello. Per esempio:

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

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

Ma i template C ++ sono essenzialmente solo un meccanismo per la tipizzazione duck in fase di compilazione. Un errore del 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 xnon ha un foo()metodo, 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 non si trova all'interno di alcune funzioni della libreria, il messaggio di errore di runtime non è ancora così grave 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 ++ fanno letteralmente scorrere la finestra della mia console per oltre 5 secondi?


6
Alcuni compilatori hanno messaggi di errore orribili, ma altri sono davvero buoni ( clang++wink wink).
Benjamin Bannier,

2
Preferiresti che i tuoi programmi fallissero in fase di esecuzione, spediti, per mano di un cliente, invece di fallire in fase di compilazione?
P:

13
@Pavel, no. Questa domanda non riguarda i vantaggi / gli svantaggi del controllo degli errori di runtime rispetto al tempo di compilazione.
Channel72,

1
Come esempio di grandi errori del modello C ++, FWIW: codegolf.stackexchange.com/a/10470/7174
keb

Risposte:


28

I messaggi di errore del modello possono essere noti, ma non sono sempre 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 nell'esempio di Python, si ottiene una "traccia dello stack" di punti di istanza del modello e un chiaro messaggio di errore che indica il problema.

A volte, i messaggi di errore relativi al modello possono richiedere molto più tempo, per vari motivi:

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

La differenza principale rispetto a Python è il sistema di tipi statici, che porta alla necessità di includere i nomi dei tipi (a volte lunghi) nel messaggio di errore. Senza di essi, a volte sarebbe molto difficile diagnosticare perché la risoluzione del sovraccarico non è riuscita. Con loro, la tua sfida non è più 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 interromperà al primo errore rilevato, 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 al primo errore nel file, poiché ciò potrebbe essere una conseguenza di un errore successivo.


4
Potresti dare un esempio di un errore che è una conseguenza di un errore successivo?
Ruslan,

12

Alcune delle ovvie ragioni includono:

  1. Storia. Quando gcc, MSVC, ecc. Erano nuovi, non potevano permettersi di usare molto spazio extra per archiviare i dati e produrre messaggi di errore migliori. La memoria era abbastanza scarsa da non poterlo fare.
  2. Per anni, i consumatori hanno ignorato la qualità dei messaggi di errore, quindi lo hanno fatto anche i venditori.
  3. Con un po 'di codice, il compilatore può risincronizzare e diagnosticare errori reali più avanti nel codice. Gli errori nei template scorrono così gravemente che qualsiasi cosa oltre il primo è quasi sempre inutile.
  4. La flessibilità generale dei modelli rende difficile indovinare ciò che probabilmente intendevi 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'istanza e la ricerca dipendente dall'argomento può aggiungere ancora più possibilità.
  6. Il sovraccarico di funzioni può fornire molti candidati a ciò a cui potrebbe riferirsi una particolare chiamata di funzione e alcuni compilatori (ad esempio, gcc) li elencano debitamente tutti quando c'è un'ambiguità.
  7. Molti programmatori che non prenderebbero mai in considerazione l'uso dei parametri normali senza assicurarsi che i valori passati soddisfino i requisiti, non provano nemmeno a controllare i parametri del modello (e devo confessare, tendo a farlo da solo).

È tutt'altro che esaustivo, ma hai l'idea generale. Anche se non è facile, la maggior parte può essere curata. Per anni, ho detto alla gente di ottenere una copia di Comeau C ++ per un uso regolare; Probabilmente ho salvato 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 è. La maggior parte del tempo, vero e proprio lavoro di un compilatore onestamente è di trasformare il codice sorgente in messaggi di errore. È giunto il momento che i fornitori si concentrino nel fare quel lavoro un po 'meglio - anche se ammetterò apertamente che quando ho scritto compilatori, ho avuto una forte tendenza a trattarlo come secondario (nella migliore delle ipotesi) e in alcuni casi quasi l'ho ignorato completamente.


9

La semplice risposta è, perché Python è stato progettato per funzionare in questo modo, mentre molte delle cose associate ai modelli sono nate per caso. Ad esempio, non è mai stato destinato a diventare un sistema completo di Turing. E se non puoi deliberatamente pianificare e ragionare 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ò renderti molto più semplice visualizzando una traccia dello stack perché sta interpretando il codice Python. Se un compilatore C ++ rileva un errore del modello e ti dà una traccia dello stack, sarebbe inutile quanto "vomito del modello", no?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.