PDA

View Full Version : [C++] Problema ereditando da una classe


Opcode
10-09-2010, 12:50
Giorno a tutti,

per gestire varie eccezioni nella mia applicazione ho definito una classe Exception, così dichiarata:

class Exception {
protected:
const char *_message; ///< Contains the error message.
const char *what(); ///< Simply returns the _message content.
public:
/// Base constructor.
Exception(const char *msg): _message(msg) {};
Exception() {};
/**
* Shows the error exploiting the WINAPI MessageBox on Windows.
* On linux prints the message on the standard output.
*/
void show();
/**
* Exactly like Exception::show but it causes the program termination.
* \warning This method causes the program termination!
*/
void fatal();
};

e definita come segue:

const char *Exception::what()
{
return _message;
}

void Exception::show()
{
std::string error(what());
error.append("\n");
error.append("Try reinstalling the application");
MessageBoxA(NULL, error.c_str(), "An error has occurred!", MB_OK);
}

void Exception::fatal()
{
std::string error(what());
error.append("\n");
error.append("Try reinstalling the application");
MessageBoxA(NULL, error.c_str(), "A fatal error has occurred!", MB_OK);
exit(EXIT_FAILURE);
}

Ho definito i metodi Exception::fatal ed Exception::show, sebbene la classe venga ereditata, per evitare di riscrivere in tutte le classi questi due metodi, qualora non ci fosse la necessità di usare qualcosa di diverso.

Ora veniamo al problema.
Ho dichiarato una classe che eredita la classe Exception (chiamata SDLException), in questa classe mi serve dichiarare anche nuovi metodi fatal ed exception perchè devono andare a recuperare il valore _message di SDLException, non quello della classe da cui ereditano, poichè non esistono overload del costruttore della classe Exception per accettare l'errore in questione.
Quindi diciamo, il principio è quello di ereditare dalla classe Exception per poter catturare l'eccezione in modo generico, ma poi chiamare il metodo "fatal" di SDLException, non quello di Exception.
Ecco la dichiarazione, seguita dalla definizione:
class SDLException: public Exception {
private:
char *_message;
const char *what();
public:
/// \param message the error message
/// \param SDLError if true describes the SDL error
SDLException(const char *message, bool SDLError);
void fatal();
void show();
};
SDLException::SDLException(const char *message, bool SDLError):
Exception(message)
{
string errorMsg(message);
if (SDLError) {
errorMsg.append("\n");
errorMsg.append(SDL_GetError());
}

_message = new char[errorMsg.size()];
errorMsg.copy(_message, errorMsg.size());
}

const char *SDLException::what()
{
return _message;
}

void SDLException::show()
{
std::string error(what());
error.append("\n");
error.append("Maybe reinstalling the game could fix this issue");
MessageBoxA(NULL, error.c_str(), "An error has occurred!", MB_OK);
}

void SDLException::fatal()
{
std::string error(what());
error.append("\n");
error.append("Maybe reinstalling the game could fix this issue");
MessageBoxA(NULL, error.c_str(), "A fatal error has occurred!", MB_OK);
exit(EXIT_FAILURE);
}

Qui i problemi sono due:
- Se non uso il costruttore Exception che accetta il messaggio come parametro (Exception::Exception(const char *), addirittura mi crasha il programma per eccezione non gestita.
- Se invece (come nella definizione precedente) passo il parametro Exception(message), l'eccezione viene gestita, ma qui:
catch (Exception &ex) {
ex.fatal();
return 0;
}
Viene chiamato il metodo Exception::fatal() non il metodo SDLException::fatal().
Ricordo che la differenza tra i due è semplicemente il messaggio che vanno a prendere, SDLException prende un messaggio costruito anche con l'errore specifico della libreria esterna (SDLException::_message), mentre Exception::fatal() no (Exception::_message).

Come potrei risolvere (magari ambedue i problemi)?
Se ritenete inadatta la corrente implementazione, sono accette anche proposte per implementazioni differenti :)

Se non sono stato chiaro chiedete pure, rispondo appena possibile.
Grazie, buona giornata.

tomminno
10-09-2010, 13:18
La classe Exception non ha i metodi dichiarati come virtual.
Ma non capisco perchè mai ridichiarare _message (tra l'altro come char*) quando è già protected in Exception?
E ancora perchè non fai derivare Exception da std::exception?
Infine non è proprio bello che un'eccezione spari fuori un messagebox.

Opcode
10-09-2010, 14:17
Anzitutto grazie della risposta. Mi hai aiutato a fixare il problema.La classe Exception non ha i metodi dichiarati come virtual. Questo mi ha permesso di utilizzare i metodi della classe figlio, ci avevo pensato già alla virtual ma non ero sicuro di poter dichiarare un metodo virtuale e poi poterlo definire (per altro colpa mia che mi son fatto venire il dubbio, dopo averlo già fatto).

Ma non capisco perchè mai ridichiarare _message (tra l'altro come char*) quando è già protected in Exception?
Anche qui, ti ringrazio, avevo fatto la modifica proprio per poterci lavorare ma mi ero dimenticato di usare quello. Probabilmente non devo lavorarci sopra quando sono stanco :stordita:

E ancora perchè non fai derivare Exception da std::exception?

Sinceramente?
Non vedo cosa mi possa offrire in più il derivare da std::exception la mia classe Exception. Sicuramente mi sbaglio, magari potrai illuminarmi. Non mi costa nulla farla derivare da quella eccezione.

Infine non è proprio bello che un'eccezione spari fuori un messagebox.
Queste eccezioni sono per un programma con interfaccia grafica, e se qualcosa và storto bisogna mostrarlo all'utente e pensavo di farlo tramite una MessageBox, tutto qui. Si capisce che se fosse una applicazione diversa, non mi verrebbe mai in mente di usare la MessageBox. Hai qualche soluzione alternativa da proporre per fare questo?

:)
Grazie ancora.

tomminno
10-09-2010, 14:55
Sinceramente?
Non vedo cosa mi possa offrire in più il derivare da std::exception la mia classe Exception. Sicuramente mi sbaglio, magari potrai illuminarmi. Non mi costa nulla farla derivare da quella eccezione.


Il fatto di non dover gestire _message e what()?
Infine, giusto prendendo spunto da i linguaggi che sono venuti dopo, male non fa derivare le eccezioni da quelle della libreria standard, qualcuno potrebbe avere nel codice un catch(const std::exception & ex) e riuscre a catturare la tua eccezione riuscendo a leggerne il messaggio d'errore.


Queste eccezioni sono per un programma con interfaccia grafica, e se qualcosa và storto bisogna mostrarlo all'utente e pensavo di farlo tramite una MessageBox, tutto qui. Si capisce che se fosse una applicazione diversa, non mi verrebbe mai in mente di usare la MessageBox. Hai qualche soluzione alternativa da proporre per fare questo?

:)
Grazie ancora.

Più che altro a livello di separazione delle responsabilità proprie della programmazione ad oggetti e di riusabilità del codice.
Se volessi utilizzare le tue classi per gestire eccezioni interne non necessariamente da mostrare all'utente?
Infine, forse nell'immediato più importante di tutte le pippe mentali riguardo alla corretta scrittura del codice, non passando l'HWND del parent al messagebox (che nel contesto dell'eccezione ovviamente non hai) l'utente potrebbe non accorgersi del messagebox in quanto potrebbe rimanere nascosto dall'interfaccia del programma, non risultando quindi bloccante. In tal caso l'utente continuerebbe ad utilizzare il programma in condizioni potenzialmente instabili.

Opcode
10-09-2010, 15:29
Il fatto di non dover gestire _message e what()?

Perdona la mia ignoranza, se sbaglio correggimi, ma la classe std::exception definisce exception::what() come:

virtual const char* what() const throw();

Quindi dovrei comunque farne la mia implementazione, e stando alla documentazione:
class exception {
public:
exception () throw();
exception (const exception&) throw();
exception& operator= (const exception&) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
}
Non ha un membro _message per salvare l'errore (giustamente è la base exception class), quindi la sola motivazione che mi sembra reggere è:
Infine, giusto prendendo spunto da i linguaggi che sono venuti dopo, male non fa derivare le eccezioni da quelle della libreria standard, qualcuno potrebbe avere nel codice un catch(const std::exception & ex) e riuscre a catturare la tua eccezione riuscendo a leggerne il messaggio d'errore.


Più che altro a livello di separazione delle responsabilità proprie della programmazione ad oggetti e di riusabilità del codice.
Se volessi utilizzare le tue classi per gestire eccezioni interne non necessariamente da mostrare all'utente?
Basterebbe catturare l'eccezione e non chiamare uno dei metodi show() o fatal()... probabilmente dovrei rendere accessibile il metodo what per ottenere l'errore in modo non intrusivo.

Infine, forse nell'immediato più importante di tutte le pippe mentali riguardo alla corretta scrittura del codice, non passando l'HWND del parent al messagebox (che nel contesto dell'eccezione ovviamente non hai) l'utente potrebbe non accorgersi del messagebox in quanto potrebbe rimanere nascosto dall'interfaccia del programma, non risultando quindi bloccante. In tal caso l'utente continuerebbe ad utilizzare il programma in condizioni potenzialmente instabili.
A questo non ci avevo pensato, ed effettivamente non hai torto, sui test che ho effettuato il popup della MessageBox ha sempre portato quest'ultima in primo piano.

tomminno
10-09-2010, 16:46
Perdona la mia ignoranza, se sbaglio correggimi, ma la classe std::exception definisce exception::what() come:

virtual const char* what() const throw();

Quindi dovrei comunque farne la mia implementazione, e stando alla documentazione:
class exception {
public:
exception () throw();
exception (const exception&) throw();
exception& operator= (const exception&) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
}
Non ha un membro _message per salvare l'errore (giustamente è la base exception class), quindi la sola motivazione che mi sembra reggere è:


In realtà potresti riuscire a comporre il messaggio d'errore nel costruttore, io ad esempio ho costruito una classe Win32Exception che compone in un metodo statico il messaggio d'errore ricavato da FormatMessageA in modo da avere un'eccezione sollevata in caso di una chiamata Win32 fallita.
Nel mio caso non è stato necessario avere nessun campo stringa aggiuntivo e tutta l'informazione è recuperabile tramite il what di exception. Magari non è esattamente il tuo caso, in ogni caso basta una variabile stringa di appoggio e ridefinire what() nella tua classe.
Io comunque ti consiglio di abbandonare l'utilizzo di stringhe C e utilizzare solamente std::string troppo più comode e soprattutto molto più sicure da utilizzare.


Basterebbe catturare l'eccezione e non chiamare uno dei metodi show() o fatal()... probabilmente dovrei rendere accessibile il metodo what per ottenere l'errore in modo non intrusivo.


Stai legando indissolubilmente tutti gli utilizzatori della tua Exception al funzionamento interno della classe. In caso di refactoring del codice qualcosa potrebbe smettere di funzionare.


A questo non ci avevo pensato, ed effettivamente non hai torto, sui test che ho effettuato il popup della MessageBox ha sempre portato quest'ultima in primo piano.

Ad esempio se l'utente ottiene un'eccezione che causa l'invocazione del metodo fatal, ma prima che questo venga invocato esegue altre azioni sulla GUI è molto probabile che il messagebox rimanga poi in secondo piano.

Opcode
11-09-2010, 21:26
In realtà potresti riuscire a comporre il messaggio d'errore nel costruttore, io ad esempio ho costruito una classe Win32Exception che compone in un metodo statico il messaggio d'errore ricavato da FormatMessageA in modo da avere un'eccezione sollevata in caso di una chiamata Win32 fallita.
Nel mio caso non è stato necessario avere nessun campo stringa aggiuntivo e tutta l'informazione è recuperabile tramite il what di exception. Magari non è esattamente il tuo caso, in ogni caso basta una variabile stringa di appoggio e ridefinire what() nella tua classe.
Io comunque ti consiglio di abbandonare l'utilizzo di stringhe C e utilizzare solamente std::string troppo più comode e soprattutto molto più sicure da utilizzare.
Seguirò i tuoi consigli. Ho già implementato la nuova classe Exception derivando da std::exception.
Riguardo l'uso delle std::string adotterò anche questo consiglio. Per ora l'unico problema, come da un più vecchio topic, è la persistenza del messaggio d'errore quando la std::string và out-of-scope (con un throw). Non mi và di utilizzare librerie esterne solo per aggirare questo problema.


Stai legando indissolubilmente tutti gli utilizzatori della tua Exception al funzionamento interno della classe. In caso di refactoring del codice qualcosa potrebbe smettere di funzionare.
Non saprei come altro fare, cioè posso fare il solo metodo che restituisce sotto forma di std::string l'errore testuale, e tutte le altre informazioni utili nel contesto di gestione dell'eccezione, ma poi toccherebbe al programmatore finale, chiamare la MessageBoxA. Quello che dici non è affatto sbagliato, anzi, però potrebbe essere snervante visto che le eccezioni derivate da Exception saranno tutte da mostrare all'utente finale.

Ad esempio se l'utente ottiene un'eccezione che causa l'invocazione del metodo fatal, ma prima che questo venga invocato esegue altre azioni sulla GUI è molto probabile che il messagebox rimanga poi in secondo piano.
Chiarissimo.

Grazie ancora, mi stai aiutando ad entrare ulteriormente nell'ottica C++.