21

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?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Charlie M
  • 273
  • 1
  • 5
  • 11
  • 10
    For a start, calling `main` recursively isn't legal. – jogojapan Apr 24 '13 at 03:29
  • `noZero` need not (or should not) be a member inside `testException`. Move it outside! – UltraInstinct Apr 24 '13 at 03:29
  • 4
    The idea is usually that you implement a separate class for each type of exception. Each of them overrides the `what()` function in a different way, and you are obviously free to include type-related information in the string it returns. – jogojapan Apr 24 '13 at 03:31
  • Wait, create a different class for each error? Doesn't that seem like a bit of overkill? Why wouldn't it be better to just create an exception class that covers the errors your looking for, instead fo a bunch of separate classes? – Charlie M Apr 24 '13 at 03:35
  • What I'm trying to see, is if you can implement some sort of variable, possibly inside whatever it is you throw, that can be read by the what and then used to decide which error to return – Charlie M Apr 24 '13 at 03:37
  • @CharlieM No it is not! You have an module-specific general exception. Every different class of exception you create, in that module, would inherit from that exception class. That way, you can have multiple catch clauses, instead of having one catch clause and a series of if-else statements. Also you can have the module-level exception caught, at the end, if you dont care about some exceptions. – UltraInstinct Apr 24 '13 at 03:44
  • So a separate header that looks like this: `class numZeroExp: public exception { public: virtual const char* what() const throw() { return "You numerator cannot be zero! Error code number 0, restarting the calculator..."; } void numZeroExp::noZero(int &a) { if(a == 0 ) throw myex; } }myex; class denZeroExp: public exception { public: virtual const char* what() const throw() { return "You can't divide by zero! Error code number 0, restarting the calculator"; } void denZeroExp::noZero(int &b) { if(b == 0 ) throw myex2; } }myex2;` – Charlie M Apr 24 '13 at 04:33
  • Basically I have two classes, both using the default exception call to what(), with separate messages. Is that correct? – Charlie M Apr 24 '13 at 04:34
  • @CharlieM Look at how `std::exception` and its hierarchy of derived classes (`std::runtime_error`, etc) are setup – Remy Lebeau Aug 26 '21 at 22:09

4 Answers4

38

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.

Baltasarq
  • 12,014
  • 3
  • 38
  • 57
  • There really is no exception class one is "advised to derive from". It's perfectly fine to use your very own exception classes if that helps you code. Just be aware that everything in `std` throws exceptions from ``. – rubenvb Apr 24 '13 at 09:51
  • 2
    It is indeed not mandatory, but it is easier (runtime_error provides a constructor for string in order to set the main error message) and potentially more significant than to derive directly from exception (since you are following the "suggested" standard exception hierarchy). Apart from that, yes, you can even throw int's if you prefer to do that. – Baltasarq Apr 24 '13 at 11:49
  • Wow. Ok, this is pretty cool, thanks. Admittedly it's going to take me a while to understand all of it, but that's the point of me practicing. I think the base class being exception is a requirement of the assignment, but I will ask if we can use the runtime_error in it's place. Our assignment after this one covers templates, so the use of them in this should be good practice as well! Thank you for the input, this should help a lot in understanding how exceptions work. – Charlie M Apr 24 '13 at 22:35
  • 1
    Oh, and everybody has been telling my not to return main, which I already know. Even my teacher had a comment or two about it when he had a look at what I was doing, but it was just a lazy way to keep the program going so I could continue to test it without having to restart it. Yeah, it's stupid practice, but I wasn't going to actually keep it in there. – Charlie M Apr 24 '13 at 22:40
  • I ended up using the runtime_error and incorporating it into my constructor, but with a string instead of an int. Doing it this way: `myException(string s) : runtime_error(s) { }` allows me to throw the constructor inside whatever function I'm testing for an error, and just input the message into the overload. Inside my address book, when the user is looking for a stored name, but it does not exist, i throw this:`throw myException("Person not in Address Book ");` inside my try catch, and I can change the message to fit the error every time! So thanks for all the help, I get it now! – Charlie M Apr 28 '13 at 05:58
  • If you're using that exception in your address book, call it AddressBookException or something else being meaningful. Yes, even it is just a test. Remember that your code reflects your way of thinking, and your abilities as a programmer and designer. I'm glad of having been of help, anyway. – Baltasarq Apr 28 '13 at 08:27
  • 3
    Won't `return cnvt.str().c_str();` in `what()` cause issues? str() returns a temp copy, c_str() points to the internal buffer in that temp, once returned the temp gets destroyed and the returned pointer (given by c_str()) is now invalid..? – user3358771 May 23 '17 at 10:26
  • That's always a possibility, but it was a constraint given the problem presented by the OP. That's why I created 'cnvt' as static, trying to mitigate the possible problems. – Baltasarq May 24 '17 at 10:57
  • 2
    @Baltasarq To regret, making 'cnvt' static neither solve nor even mitigate the problem. Static 'cnvt' still created a local string object by 'str()' method, then you return a pointer to the internals of the latter. Also, << operators can throw an exception which violates your throw() specification. – Arthur P. Golubev Aug 01 '17 at 13:18
  • @Baltasarq making `cnvt` static won't solve the issue of `str()` returning temporary `std::string`. You should make `cnvt` be a `std::string` instead of a `std::ostringstream`, and not `static`. You should assign the value of `cvnt` in the exception's constructor, not inside of `what()` – Remy Lebeau Aug 26 '21 at 22:11
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)){}
};
1
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();
 }
kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
John Kemp
  • 149
  • 1
  • 2
  • 11
  • If I use `catch(std::exception &e)` then I would be stuck with the default .what output – Charlie M Apr 24 '13 at 04:32
  • 2
    No because you would create a class class zeroNumerator : public std::exception { const char* what() const throw() { return "Numerator can't be 0" }; – John Kemp Apr 24 '13 at 06:26
  • 1
    it will catch the zeroNumerator because it's the same try and catch block...it knows what to catch...try it if you don't trust me – John Kemp Apr 24 '13 at 06:28
1

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 be 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 CalculationError : public std::runtime_error {
public:
    CalculationError(const char * message)
        :runtime_error(message)
    {
    }
};


class ZeroDeviderError : public CalculationError {
public:
    ZeroDeviderError(int numerator, const char * message)
        : CalculationError(message) 
        , numerator (numerator)
    {
    }
    int GetNumerator() const { return numerator; }
private:
    const int numerator;
};
  • 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 might want to be specific

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

in others, not

} catch (const CalculationError & ex) {
    // ...
}

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 the catch section (don't be confused by access via reference - another instance of the exception object is created when throwing)
  • Using a const reference would be a good style catch (const testException &te) unless you really need a non-constant object.

Also, please note that the type (classes) used for exceptions are not permitted to throw exceptions out of their copy constructors since, if the initial exception is attempted to be caught by value, a call of copy constructor is possible (in case is not elided by the compiler) and this additional exception will interrupt the initial exception handling before the initial exception is caught, which causes calling std::terminate. Since C++11 compilers are permitted to eliminate the copying in some cases when catching, but both the elision is not always sensible and, if sensible, it is only permission but not obligation (see https://en.cppreference.com/w/cpp/language/copy_elision for details; before C++11 the standards of the language didn’t regulate the matter).

Also, you should avoid exceptions (will call them the additional) to be thrown out of constructors and move constructors of your types (classes) used for exceptions (will call them initial) since the constructors and move constructors could be called when throwing objects of the types as initial exceptions, then throwing out an additional exception would prevent creation of an initial exception object, and the initial would just be lost. As well as an additional exception from a copy constructor, when throwing an initial one, would cause the same.