Chi programma in Java o C# conosce la potenza dei costrutti
try/catch/finally.
Sopprattutto dell'utilizzo del
finally, dove e' possibile eseguire eventuali operazioni di cleanup sia che il blocco
try sia terminato normalmente, sia con eccezione.
Le altre due funzionalita'
estremamente utili e potenti nella gestione delle eccezioni sono:
1) la possibilita' di ricuperare il callstack del punto in cui un'eccezione e' stata generata
2) la possibiliita' di includere all'interno di un'eccezione, un'eventuale altra eccezione
Attenzione, non chiedete 'ma serve?' Se vi ponete questa domanda, e' come se vi chiedeste 'ma l'aria serve?'
In C++ non esiste il
finally (a meno di non usare le estensioni di Micro$oft, che comunque sono ancora limitate). E non c'e' un meccanismo standard per includere in'eccezione in un'altra.
Nota: In Java e C# e' estremamente piu' semplice implementare l'inclusione delle eccezioni perche' nella maggior parte dei case gli oggetti sono gestiti mediante reference e non per value.
In C++ il
finally generalmente viene implementato mediante il pattern
Resource Acquisition Is Initialization: cioe' si utilizza un oggetto di servizio in cui:
1) nel costruttore viene allocata la risorsa
2) nel distruttore viene rilasciata.
Ora, quando si esce da un blocco "{ ... }", il C++ assicura la chiamata dei distruttori degli oggetti ivi allocati. E questo anche se si esce per eccezione.
Quindi, come simulare il
try/catch/finally?
Una possibilita' e' questa:
/*
* File: trycatch.hpp
* Author: ...
*
* Created on April 24, 2013, 10:45 AM
*/
#ifndef TRYCATCH_HPP
#define TRYCATCH_HPP
#include <exception>
#include <string>
#include <vector>
/**
* _try { ... }
* _end_try
*
* _try { ... }
* _catch(e) { ... }
* _catch_all { ... }
* _end_catch
*
* _try { ... }
* _finally { ... }
* _end_finally
*
* NON supportato:
*
* _try { ... }
* _catch(e) { ... }
* _catch_all{ ... }
* _finally { ... }
* _end_finally
*/
namespace hls
{
class try_t {
/**
* Usato per mantenere traccia del top dello stach dei try/catch
*
* Nota: DEVE essere un threadlocal perche' le relazioni tra i thread
* generano un tree.
*/
static try_t try_end;
static __thread try_t* try_top;
/// se e' stato istanziato in una try
bool _is_try;
/// se l'eccezione E' stata istanziata per forzare la 'finalize'
bool _is_finally;
/// link al try precendete
try_t* _prev;
/// nome del file/linea/nome della funzione
const char* _filename;
int _line;
const char* _function;
/// link al precedente try
try_t* prev() const { return _prev; }
/// la classe 'exception_t' puo' accedere alle informazioni di
/// questa classe
friend class exception_t;
public:
/// costruttore usato nella macro '_try'
try_t(const char* fn, int l, const char* fu);
try_t();
try_t(const try_t& t);
~try_t();
try_t& operator =(const try_t& t);
const char* filename() const { return _filename; }
int line() const { return _line; }
const char* function() const { return _function; }
void finalize();
bool is_finally() const { return _is_finally; }
};
class exception_t : std::exception {
std::vector<try_t> _callstack;
std::string _what;
void fill_stack();
public:
exception_t();
exception_t(const char* msg);
exception_t(const std::string& msg);
virtual ~exception_t() throw ();
virtual const char* what() const throw() { return _what.c_str(); }
std::vector<try_t>& callstack() const { return (std::vector<try_t>&)_callstack; }
void set(const char* fn, int l, const char* fu);
};
}
#define _try { hls::try_t try_info(__FILE__,__LINE__,__FUNCTION__); try {
#define _end_try } catch(...) { }}
#define _catch(e) } catch(e) {
#define _catch_all } catch(...) {
#define _end_catch }}
#define _finally try_info.finalize(); } catch(...) {
#define _end_finally if (!try_info.is_finally()) throw; }}
#endif /* TRYCATCH_HPP */
/*
* File: trycatch.cpp
* Author: ...
*
* Created on April 24, 2013, 10:48 AM
*/
#include "trycatch.hpp"
using namespace hls;
__thread try_t* try_t::try_top = 0;
// --------------------------------------------------------------------------
// try_t
// --------------------------------------------------------------------------
try_t::try_t(const char* fn, int l, const char* fu)
: _filename(fn), _line(l), _function(fu)
{
_is_try = true;
_prev = try_top;
try_top = this;
}
try_t::try_t()
: _filename(0), _line(0), _function(0)
{
_is_try = false;
_prev = 0;
}
try_t::try_t(const try_t& t)
: _filename(t._filename), _line(t._line), _function(t._function)
{
_is_try = false;
_prev = 0;
}
try_t::~try_t()
{
if (_is_try)
try_top = _prev;
}
try_t& try_t::operator =(const try_t& t) {
_filename = t.filename();
_line = t.line();
_function = t.function();
_prev = 0;
return *this;
}
void try_t::finalize() {
_is_finally = true;
throw this;
}
// --------------------------------------------------------------------------
// exception_t
// --------------------------------------------------------------------------
exception_t::exception_t() {
fill_stack();
}
exception_t::exception_t(const char* msg) {
_what = msg;
fill_stack();
}
exception_t::exception_t(const std::string& msg) {
_what = msg;
fill_stack();
}
exception_t::~exception_t() throw () {
_callstack.clear();
}
// --------------------------------------------------------------------------
void exception_t::fill_stack() {
try_t here;
_callstack.push_back(here);
for(try_t* top = try_t::try_top; top != 0; top=top->prev())
_callstack.push_back(*top);
}
void exception_t::set(const char* fn, int l, const char* fu) {
try_t& here = _callstack[0];
here._filename = fn;
here._line = l;
here._function = fu;
}
A partire da questa libreria, si puo' scrivere:
/*
* File: main.cpp
* Author: ...
*
* Created on April 21, 2013, 9:32 AM
*/
#include <cstdlib>
#include <iostream>
#include <boost/format.hpp>
#include <hls/trycatch.hpp>
#include <exception>
using namespace std;
using namespace boost;
/*
*
*/
int main(int argc, char** argv) {
_try
{
hls::exception_t e("I am dead!");
_try
{
e.set(__FILE__,__LINE__,__FUNCTION__);
std::cout << boost::format("%1%\n") % "Hello Cruel World";
//throw e;
}
_finally
{
std::cout << boost::format("%1%\n") % "Finally";
}
_end_finally
throw e;
//std::cout << boost::format("%1%\n") % "Hello Cruel World";
}
_catch(hls::exception_t& e) {
std::cout << boost::format("-) %1%:%2% %3%\n") % try_info.filename() % try_info.line() % try_info.function();
std::vector<hls::try_t>& cs = e.callstack();
std::cout << "Callstack:\n";
for(std::vector<hls::try_t>::iterator it=cs.begin(); it != cs.end(); ++it) {
std::cout << boost::format("-) %1%:%2% %3%\n") % it->filename() % it->line() % it->function();
}
}
_catch(std::exception& e) {
std::cout << boost::format("a) %1%:%2% %3%\n") % try_info.filename() % try_info.line() % try_info.function();
}
_catch_all {
std::cout << boost::format("b) %1%:%2% %3%\n") % try_info.filename() % try_info.line() % try_info.function();
}
_end_catch
return 0;
}
(nota: al posto della libreria boost, si puo' utilizzare l'operatore "<<" con "std::cout")
Al momento, la libreria supporta
solo le seguenti sintassi:
_try { ... } _end_try
intercetta
tutte le eccezione,e non le propaga
_try { ... } _catch(e) { ... } _catch_all { ... } _end_catch
intercetta l'eccezione indicata (o
tutte le eccezioni)
try { ... } _finally { ... } _end_finally
la sezione
finally viene eseguita sia in presenza di eccezioni, che in assenza. Se e' stata generata un'eccezione, questa viene propagata.
Ed ora la domandona:
si puo' fare di meglio?