web-dev-qa-db-fra.com

Comment créer des exceptions?

J'ai donc une mission à venir portant sur les exceptions et les utilisant dans mon programme de carnet d'adresses actuel, qui concentre la plupart des devoirs. J'ai décidé de jouer avec les exceptions et tout le truc de catch catch, et d'utiliser un design de classe, ce que je devrai finalement faire pour mon devoir dans quelques semaines. J'ai un code de travail qui vérifie très bien l'exception, mais ce que je veux savoir, c'est s'il existe un moyen de normaliser ma fonction de message d'erreur, (c'est-à-dire mon appel what ()):

Voici mon code:

#include <iostream>
#include <exception>
using namespace std;


class testException: public exception
{
public:
  virtual const char* what() const throw() // my call to the std exception class function (doesn't nessasarily have to be virtual).
  {
  return "You can't divide by zero! Error code number 0, restarting the calculator..."; // my error message
  }

  void noZero();

}myex;  //<-this is just a lazy way to create an object



int main()
{
void noZero();
int a, b;

cout << endl;

cout << "Enter a number to be divided " << endl;

cout << endl;

cin >> a;

cout << endl;

cout << "You entered " << a << " , Now give me a number to divide by " << endl;

cin >> b;

try
{    
    myex.noZero(b); // trys my exception from my class to see if there is an issue
}
catch(testException &te) // if the error is true, then this calls up the eror message and restarts the progrm from the start.
{
    cout << te.what() << endl;
    return main();
}

cout <<endl;

cout << "The two numbers divided are " << (a / b) << endl;  // if no errors are found, then the calculation is performed and the program exits.

return 0;

}

  void testException::noZero(int &b) //my function that tests what I want to check
  { 
    if(b == 0 ) throw myex;  // only need to see if the problem exists, if it does, I throw my exception object, if it doesn't I just move onto the regular code.
  }

Ce que j'aimerais pouvoir faire, c'est que ma fonction what () puisse renvoyer une valeur dépendant du type d'erreur appelé. Ainsi, par exemple, si j'appelais une erreur qui ressemblait à un nombre supérieur, (a), pour voir si c'était un zéro, et si c'était le cas, il définirait alors le message pour dire que "vous ne pouvez pas avoir un numérateur de zéro ", mais toujours dans la fonction what (). Voici un exemple:

  virtual const char* what() const throw() 
  if(myex == 1)
  {
      return "You can't have a 0 for the numerator! Error code # 1 "
  }
  else

  return "You can't divide by zero! Error code number 0, restarting the calculator..."; // my error message
  }

Cela ne fonctionnerait évidemment pas, mais existe-t-il un moyen de le faire, donc je n'écris pas une fonction différente pour chaque message d'erreur?

20
Charlie M

Votre code contient de nombreuses idées fausses. La réponse courte est oui, vous pouvez changer what() afin de retourner ce que vous voulez. Mais allons-y étape par étape.

#include <iostream>
#include <exception>
#include <stdexcept>
#include <sstream>
using namespace std;


class DivideByZeroException: public runtime_error {
public:

  DivideByZeroException(int x, int y)
    : runtime_error( "division by zero" ), numerator( x ), denominator( y )
    {}

  virtual const char* what() const throw()
  {
    cnvt.str( "" );

    cnvt << runtime_error::what() << ": " << getNumerator()
         << " / " << getDenominator();

    return cnvt.str().c_str();
  }

  int getNumerator() const
    { return numerator; }

  int getDenominator() const
    { return denominator; }

  template<typename T>
  static T divide(const T& n1, const T& n2)
    {
        if ( n2 == T( 0 ) ) {
            throw DivideByZeroException( n1, n2 );
        } 

        return ( n1 / n2 );
    }

private:
    int numerator;
    int denominator;

    static ostringstream cnvt;
};

ostringstream DivideByZeroException::cnvt;

En premier lieu, runtime_error, Dérivé de exception, est la classe d'exception conseillée à dériver. Ceci est déclaré dans l'en-tête stdexcept. Vous n'avez qu'à initialiser son constructeur avec le message que vous allez retourner dans la méthode what().

Deuxièmement, vous devez nommer correctement vos classes. Je comprends que ce n'est qu'un test, mais un nom descriptif aidera toujours à lire et à comprendre votre code.

Comme vous pouvez le voir, j'ai changé de constructeur afin d'accepter les nombres à diviser qui ont provoqué l'exception. Vous avez fait le test dans l'exception ... eh bien, j'ai respecté cela, mais comme une fonction statique qui peut être invoquée de l'extérieur.

Et enfin, la méthode what(). Puisque nous divisons deux nombres, il serait bien de montrer que deux nombres qui ont provoqué l'exception. La seule façon d'y parvenir est d'utiliser ostringstream. Ici, nous le rendons statique, il n'y a donc aucun problème à renvoyer un pointeur vers un objet de pile (c'est-à-dire qu'avoir cnvt une variable locale introduirait un comportement indéfini).

Le reste du programme est plus ou moins tel que vous l'avez indiqué dans votre question:

int main()
{
int a, b, result;

cout << endl;

cout << "Enter a number to be divided " << endl;

cout << endl;

cin >> a;

cout << endl;

cout << "You entered " << a << " , Now give me a number to divide by " << endl;

cin >> b;

try
{    
        result = DivideByZeroException::divide( a, b );

    cout << "\nThe two numbers divided are " << result << endl;
}
catch(const DivideByZeroException &e)
{
    cout << e.what() << endl;
}

return 0;

}

Comme vous pouvez le voir, j'ai supprimé votre instruction return main(). Cela n'a aucun sens, car vous ne pouvez pas appeler main() récursivement. De plus, l'objectif est une erreur: vous vous attendez à recommencer l'opération qui a provoqué l'exception, mais ce n'est pas possible, car les exceptions ne sont pas réentrantes. Vous pouvez cependant modifier un peu le code source pour obtenir le même effet:

int main()
{
int a, b, result;
bool error;

do  {
    error = false;

    cout << endl;

    cout << "Enter a number to be divided " << endl;

    cout << endl;

    cin >> a;

    cout << endl;

    cout << "You entered " << a << " , Now give me a number to divide by " << endl;

    cin >> b;

    try
    {    
        result = DivideByZeroException::divide( a, b ); // trys my exception from my class to see if there is an issue

        cout << "\nThe two numbers divided are " << result << endl;
    }
    catch(const DivideByZeroException &e) // if the error is true, then this calls up the eror message and restarts the progrm from the start.
    {
        cout << e.what() << endl;
        error = true;
    }
} while( error );

return 0;

}

Comme vous pouvez le voir, en cas d'erreur, l'exécution suit jusqu'à ce qu'une division "correcte" soit entrée.

J'espère que cela t'aides.

37
Baltasarq

Vous pouvez créer votre propre classe d'exception pour les erreurs de longueur comme celle-ci

class MyException : public std::length_error{
public:
  MyException(const int &n):std::length_error(to_string(n)){}
};
3
Shashank Chaurasia
class zeroNumerator: public std::exception
{
    const char* what() const throw() { return "Numerator can't be 0.\n"; }
};

//...

try
{
  myex.noZero(b); // trys my exception from my class to see if there is an issue
  if(myex==1)
  {
     throw zeroNumerator(); // This would be a class that you create saying that you can't have 0 on the numerator
  }

}
catch(testException &te) 
{
   cout << te.what() << endl;
   return main();
}

Vous devez toujours utiliser std :: exception & e. alors faites

catch(std::exception & e)
{
  cout<<e.what();
 }
2
John Kemp

Vous devriez considérer une hiérarchie de classes.

La raison peut ne pas être évidente lorsque vous essayez d'utiliser des exceptions uniquement pour le transfert d'une chaîne, mais l'intention réelle d'utiliser des exceptions devrait être un mécanisme de gestion avancée des situations exceptionnelles. Beaucoup de choses se font sous le capot de l'environnement d'exécution C++ tandis que la pile d'appels est déroulée lors du passage de "lancer" à "attraper" correspondant.

Un exemple des classes pourrait être:

class DivisionError : public std::exception {
public:
    DevisionError(const int numerator, const int divider)
        :numerator(numerator)
        , divider(divider)
    {
    }
    virtual const char* what() const noexcept {
        // ...
    }
    int GetNumerator() const { return numerator; }
    int GetDevider() const { return divider; }
private:
    const int numerator;
    const int divider;
};


class BadNumeratorError : public DivisionError {
public:
    BadNumeratorError(int numerator, int divider)
        : DivisionError(numerator, divider) 
    {
    }
    virtual const char* what() const noexcept {
    {
        // ...
    }
};


class ZeroDeviderError : public DivisionError {
public:
    ZeroDeviderError(int numerator, int divider)
        : DivisionError(numerator, divider)
    {
    }
    virtual const char* what() const noexcept {
    {
        // ....
    }
};
  • En fournissant différentes classes pour les erreurs, vous donnez aux développeurs la possibilité de gérer différentes erreurs de manière particulière (pas seulement d'afficher un message d'erreur)
  • Fournir une classe de base pour les types d'erreur, permet aux développeurs d'être plus flexibles - être aussi spécifiques qu'ils en ont besoin.

Dans certains cas, ils doivent être spécifiques

} catch (const ZeroDividerError & ex) {
// ...
} catch (const DivisionError & ex) {

dans d'autres, ne

} catch (const DivisionError & ex) {

Quant à quelques détails supplémentaires,

  • Vous ne devez pas créer des objets de vos exceptions avant de lancer de la même manière. Quelle que soit votre intention, c'est juste inutile - de toute façon, vous travaillez avec une copie de l'objet dans la section catch (ne soyez pas confondu par l'accès via une référence)
  • Utiliser une référence const serait un bon style catch (const testException &te) sauf si vous avez vraiment besoin d'un objet non constant.
1
Arthur P. Golubev