How to create exceptions?

泪湿孤枕 提交于 2019-12-29 03:39:06

问题


So I have an upcoming assignment dealing with exceptions and using them in my current address book program that most of the homework is centered around. I decided to play around with exceptions and the whole try catch thing, and using a class design, which is what I will eventually have to do for my assignment in a couple of weeks. I have working code that check the exception just fine, but what I want to know, is if there is a way to standardize my error message function, (i.e my what() call):

Here s my 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.
  }

What I would like to be able to do is make it so my what() function can return a value dependent on what type of error is being called on. So for instance, if I were calling up an error that looked a the top number,(a), to see if it was a zero, and if it was, it would then set the message to say that "you can't have a numerator of zero", but still be inside the what() function. Here's an example:

  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
  }

This obviously wouldn't work, but is there a way to make it so I'm not writing a different function for each error message?


回答1:


Your code contains a lot of misconceptions. The short answer is yes, you can change what() in order to return whatever you want. But let's go step by step.

#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;

In the first place, runtime_error, derived from exception, is the adviced exception class to derive from. This is declared in the stdexcept header. You only have to initialize its constructor with the message you are going to return in the what() method.

Secondly, you should appropriately name your classes. I understand this is just a test, but a descriptive name will always help to read and understand your code.

As you can see, I've changed the constructor in order to accept the numbers to divide that provoked the exception. You did the test in the exception... well, I've respected this, but as a static function which can be invoked from the outside.

And finally, the what() method. Since we are dividing two numbers, it would be nice to show that two numbers that provoked the exception. The only way to achieve that is the use of ostringstream. Here we make it static so there is no problem of returning a pointer to a stack object (i.e., having cnvt a local variable would introduce undefined behaviour).

The rest of the program is more or less as you listed it in your 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;

}

As you can see, I've removed your return main() instruction. It does not make sense, since you cannot call main() recursively. Also, the objective of that is a mistake: you'd expect to retry the operation that provoked the exception, but this is not possible, since exceptions are not reentrant. You can, however, change the source code a little bit, to achieve the same effect:

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;

}

As you can see, in case of an error the execution follows until a "proper" division is entered.

Hope this helps.




回答2:


You can create your own exception class for length error like this

class MyException : public std::length_error{
public:
  MyException(const int &n):std::length_error(to_string(n)){}
};



回答3:


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();
}

You should always use std::exception&e. so do

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



回答4:


You should consider a hierarchy of classes.

The reason for it might not be obvious when trying to use exceptions just for transferring a string, but actual intent of using exceptions should a mechanism for advanced handling of exceptional situations. A lot of things are being done under the hood of C++ runtime environment while call stack is unwound when traveling from 'throw' to corresponded 'catch'.

An example of the classes could be:

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 {
    {
        // ....
    }
};
  • Providing different classes for the errors, you give developers a chance to handle different errors in particular ways (not just display an error message)
  • Providing a base class for the types of error, allows developers to be more flexible - be as specific as they need.

In some cases, they need to be specific

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

in others, don't

} catch (const DivisionError & ex) {

As for some additional details,

  • You should not create objects of your exceptions before throwing in the manner you did. Regardless your intention, it is just useless - anyway, you are working with a copy of the object in catch section (don't be confused by access via reference)
  • Using a const reference would be a good style catch (const testException &te) unless you really need a non-constant object.


来源:https://stackoverflow.com/questions/16182781/how-to-create-exceptions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!