14

I have an overloaded operator in my Fraction class in C++ which is designed to take input from standard input in the form of an integer over an integer i.e. 1/2 or 32/4 and initialize a Fraction object based off of those values. It works, but I'm having trouble catching errors.

// gets input from standard input in the form of (hopefully) int/int
std::istream& operator >>(std::istream& inputStream, Fraction& frac)
{
    int inputNumerator, inputDenominator;
    char slash;

    if ((std::cin >> inputNumerator >> slash >> inputDenominator) && slash == '/')
    {
        if (inputDenominator == 0)
        {
            std::cout << "Denominator must not be 0." << std::endl;
            throw std::invalid_argument("Denominator must not be 0.");
        }
        frac.numerator = inputNumerator;
        frac.denominator = inputDenominator;

        frac.reduce();
    }
    else
    {
        throw std::invalid_argument("Invalid syntax.");
    }



    return inputStream;
}

I invoke the method like this:

 Fraction frac(1, 2);


while (true)
{
    try {
        std::cout << "Enter a fraction in the form int/int: ";
        std::cin >> frac;
        std::cout << frac;
    } catch (std::invalid_argument iaex) {
        std::cout << "Caught an error!" << std::endl;
    }
}

But whenever an error is thrown, (I'll enter something like garbage) this causes the loop to go on forever without asking for input. What is the reason for this?

michaelsnowden
  • 6,031
  • 2
  • 38
  • 83
  • A little comment besides on your code: if you write an input `operator<<`, you should use the srteam passed to it rather than falling back to `std::cin`. Then this can work as expected: `ifstream if(somefile.txt); if >> frac;` – Arne Mertz Feb 11 '14 at 08:44
  • You also might want to catch exceptions by reference, not by value. – BitTickler Jan 06 '15 at 17:16
  • Adding to what @BitTickler said, the rule-of-thumb for this is: "Throw by **value**, catch by **reference**" – Scott Smith Jun 09 '21 at 08:09

1 Answers1

20

Since you don't tell us what input you give to your program, I can only guess:

You type something that cin can't parse as the sequence inputNumerator >> slash >> inputDenominator, so it goes into a fail state or bad state. The exception gets thrown in the else branch of your function (you can and should test that by printing the exception's message in your main function's catch block).

Your loop in main goes on, but cin is left in the bad state, so every following input fails again and the same exception is thrown again and again. To prevent that you should clear the state of cin during the error handling. In addition, if there are bad characters in the input stream that can't be parsed, they have to be thrown out, or ignored:

if ((std::cin >> inputNumerator >> slash >> inputDenominator) && slash == '/')
{
  /* ... */
}
else
{
    std::cin.clear(); // reset the fail flags
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); //ignore the bad input until line end
    throw std::invalid_argument("Invalid syntax.");
}
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • I edited my question a little bit. I can enter any sort of bad input and this will happen. For example `garbage` as input or `steveaoki`. Anyway, I tried this and it didn't work. I am thinking it might have something to do with the fact that I have an inputstream in the function. – michaelsnowden Feb 11 '14 at 07:59
  • @doctordoder I edited the answer, I missed the `ignore` of the bad input first. – Arne Mertz Feb 11 '14 at 08:05
  • I actually just found a thread that has the same answer and it worked for me, so yes I can confirm this. Thank you! – michaelsnowden Feb 11 '14 at 08:07
  • Any idea why it would all fail if I entered just 0? – michaelsnowden Feb 11 '14 at 08:11
  • @doctordoder it would fail to read the slash and the denominator you explicitly want. Making those optional requires a bit more work. – Arne Mertz Feb 11 '14 at 08:42