Esperimento estivo: uno scheletro di "conditions system" vagamente alla Common Lisp per C++
https://bitbucket.org/mitalia/condition_cpp/
L'idea di fondo è che in molti casi le eccezioni sono inadeguate, perché nel momento in cui l'eccezione raggiunge un handler lo stack è già stato smontato, e l'operazione che era in corso è stata di fatto annullata. Se anche l'handler sa come correggere il problema (ad esempio, provare a riaprire un socket, o fornire un file alternativo da leggere) di fatto deve far ripartire l'operazione da capo; se l'intelligenza che serve per prendere queste decisioni è a livello molto alto (addirittura potrebbe essere un handler globale a livello di applicazione), far ripartire l'operazione dopo aver intrapreso l'azione correttiva può essere estremamente complicato.
Per questo motivo in Common Lisp esiste il concetto di "conditions" invece delle eccezioni. Una condition è (in genere) una condizione imprevista che si verifica durante un'elaborazione; il codice che segnala una condizione non fa necessariamente un abort fino al punto in cui è stato stabilito un handler come nel caso delle eccezioni, ma piuttosto invoca l'handler dicendogli cosa non va; la differenza fondamentale è qui: l'handler viene eseguito in cima allo stack, senza che sia già stato buttato via tutto, per cui può effettivamente cercare di fare qualcosa per aggiustare il problema e proseguire (invece di ricominciare da capo).
In Common Lisp in genere il codice che segnala la condition fornisce dei restart point, ovvero delle closure che il codice dell'handler può invocare; per esempio, un'apertura di file fallita può mettere a disposizione come restart point per l'handler una funzione da chiamare per fornire un altro nome file da provare, un file descriptor già aperto o fare un abort.
La mia libreria non fa nulla di tutto questo: si limita a fornire un sistema comodo per stabilire degli handler (da allocare tassativamente sullo stack), che vengono invocati in ordine inverso di costruzione passandogli l'oggetto-condition finché qualcuno non lo prende in carico e intraprende una eventuale azione correttiva (ad esempio, può scrivere nell'oggetto-condition un nome file alternativo da aprire). A questo punto, la palla torna al codice che ha segnalato la condizione, e, se questa è stata gestita da qualcuno, può proseguire.
La sintassi attuale è di questo tipo: una condition è una classe che eredita da Condition. In questo caso, definiamo DivByZero come classe che eredita da ArithmeticError, che a sua volta eredita da Condition; DivByZero fornisce come azione correttiva un replacementValue da usare come risultato della divisione.
codice:
struct ArithmeticError : Condition { };
struct DivByZero : ArithmeticError { double replacementValue; };
Per segnalare la condition, si usa signal_condition, che restituisce true se un handler l'ha gestita.
codice:
double divide(double num, double den) {
if(den == 0.) {
DivByZero cond;
if(!signal_condition(cond)) {
throw std::invalid_argument("division by zero");
}
return cond.replacementValue;
}
return num/den;
}
A livello superiore può essere stato definito un handler; cond_filter automaticamente filtra per tipo di condizione, facendo in modo che la lambda che gestisce la cosa "veda" solo le condition di un certo tipo:
codice:
void foo() {
Handler h(cond_filter<DivByZero>([] (DivByZero *cond) {
cond->replacementValue = 15;
return true;
}));
// prints 15
std::cout<<divide(10., 0.)<<"\n";
}
un handler può anche restituire sempre false e limitarsi a "vedere passare" le eventuali condition che lo attraversano:
codice:
Handler h(cond_filter<ArithmeticError>([] (ArithmeticError *cond){
std::cerr<<"There's an ArithmeticError passing here! "<<typeid(*cond).name()<<"\n";
return false;
}));
Per maggiori info e un esempietto, c'è tutto al link postato sopra.