Thread: To throw or not to throw

  1. #1
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58

    To throw or not to throw

    This is something that I've been curious about.

    What's your opinion about exceptions? Do you usually use them in your programs (those where you can write code as you like and are not tied to some team's coding standard)?

    Recently I've been using Qt a lot and I noticed that they don't use exceptions. Considering that the library is pretty big, it seems to be working out alright for them.

    How do you handle errors in your code?

  2. #2
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    I usually use asserts() and printf() or std::cout.

    I've seen one person use assert(some_false_condition && "Error message here!");

    Edit :

    Actually, I really do like this method.
    Code:
    #include <cassert>
    #include <iostream>
    
    
    int main(void)
    {
        assert(true && "Error message!\n");
    
    
        assert(false && "This one is different\n");
    
    
        return 0;
    }
    Last edited by MutantJohn; 01-21-2015 at 01:25 PM.

  3. #3
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by koplersky
    What's your opinion about exceptions? Do you usually use them in your programs (those where you can write code as you like and are not tied to some team's coding standard)?
    I use them where appropriate. You could read Sutter and Alexandrescu's C++ Coding Standards for suggestions on where they are appropriate.

    Quote Originally Posted by MutantJohn
    I usually use asserts() and printf() or std::cout.
    These serve different purposes from exceptions.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  4. #4
    Unregistered User Yarin's Avatar
    Join Date
    Jul 2007
    Posts
    2,158
    Quote Originally Posted by koplersky View Post
    What's your opinion about exceptions? Do you usually use them in your programs (those where you can write code as you like and are not tied to some team's coding standard)?
    I use exceptions very liberally; it helps a lot with readability. It's aboslutely amazing how cluttered C and Go programs are with error propegation ceremony; not to mention how many of said programs silently ignore errors, which has the potential to lead to a UX nightmare.

    My rule is, functions must only return what they are expected to return, if it can't for what ever reason, throw.

    For example, any function named "findX" should find X, or throw if it doesn't. But perhaps in some situations you're okay with the function being unable to find X (this is common in string processing), the function should be then be named "findXIfExists" or similar.

  5. #5
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Quote Originally Posted by laserlight View Post
    These serve different purposes from exceptions.
    I don't negotiate with code that doesn't do what I want it to do, hence asserts.

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by MutantJohn
    I don't negotiate with code that doesn't do what I want it to do, hence asserts.
    You're probably thinking of those uses of exceptions that check for programming logic errors, things like pre-condition violations for function arguments (an example that comes to mind would be operator[] versus at() for std::vector: at() will throw for an out of range index, operator[] won't, but an assertion might be used anyway). However, an exception could plausibly be used to say, report a configuration error. Using an assertion for such a task would be foolish since it is not a bug but rather a kind of input error.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Oh, I hear you now. Now that's a clever use.

  8. #8
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I use exceptions because they make the flow of errors so much easier to read and parse (plus 3rd party libraries throw them too) than typical C handling.
    I really like functions where you can choose to have it either return an error or throw an exception, because no matter how well you design it, there's always going to be those times when returning an error is going to result in cleaner code than always throwing exceptions, and there are always going to be times where returning an error (I'm look at you, standard library algorithms!) will result in more verbose code.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  9. #9
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    Alright, thank you guys for the answers!

  10. #10
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by Elysia View Post
    I really like functions where you can choose to have it either return an error or throw an exception, because no matter how well you design it, there's always going to be those times when returning an error is going to result in cleaner code than always throwing exceptions, and there are always going to be times where returning an error (I'm look at you, standard library algorithms!) will result in more verbose code.
    If libraries need to do that, I would suggest that their error handling strategy isn't properly sorted out.

    Generally speaking, I will use error codes to report recoverable status information. By this I mean error conditions or other information for which some circumstances can be safely ignored, and calling code MIGHT or MIGHT NOT wish to test for particular conditions (an example would be encountering the end when reading a file) without having the sky fall in. Exceptions, on the other hand, I use for reporting critical errors - if critical errors are not able to be handled, there are few options for a program other than termination (an example is a failure to allocate memory, or failure to construct an object that is needed for a program to function).

    An I/O library, for example, is one that I would argue should only return error codes. Yes, code that does I/O (say, reading a file) might decide that a I/O error is critical (for example, reading a mandatory configuration file) but THAT code can then elect to throw an exception - which forces the calling program to either recover from the problem, or be terminated.

    Very few algorithms in the C++ standard library throw exceptions at all. A number of them accept functions (or functors) which can throw an exception, but these are under control of the programmer, not the standard. The standard simply states requirements for what happens if one of those programmer-supplied functions throws an exception (e.g. to avoid data corruption due to destroying improperly constructed objects). In the end, however, it is up to the programmer - when providing a function to an algorithm - to provide functions that behave rationally. That is the price of the C++ algorithms being generic, and providing hooks the programmer can use.

    In most circumstances, making the RIGHT choice (i.e. a balanced trade-off of competing concerns) when designing the library between error codes, exceptions, or other error reporting approach does more to simplify calling code than allowing the caller to determine how errors are reported. Yes, there are edge cases where two choices might be equally "RIGHT" but, more often than not, those reflect poorly structured calling code (e.g. code in which error handling is an after-thought, or code that is written in a way that triggers errors in the library by breaking the rules).
    Last edited by grumpy; 01-22-2015 at 04:11 PM.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  11. #11
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by grumpy View Post
    An I/O library, for example, is one that I would argue should only return error codes. Yes, code that does I/O (say, reading a file) might decide that a I/O error is critical (for example, reading a mandatory configuration file) but THAT code can then elect to throw an exception - which forces the calling program to either recover from the problem, or be terminated.
    Which leads us back to the whole point that an I/O error can be an error, and it can also not be an error. If the library only returns error codes, then the client may have to make their code more verbose by adding possible if/then/throw at every point where an I/O call is made. If the library throws an error, then the client may have to add try/catch around every non-fatal error, again making it verbose. So if the library allows for both ways of reporting the error, the client code becomes less verbose, easier to read and maintain. That's why I argue for both approaches.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  12. #12
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by Elysia View Post
    Which leads us back to the whole point that an I/O error can be an error, and it can also not be an error.
    Not really. The context in which an I/O error is critical is in its context of use, not in the act of doing I/O.

    It is possible to recover from finding that a file does not exist (e.g. by finding another file). It is possible to recover from finding that a file contains the string "Dogs Breakfast" when trying to read a decimal digit (e.g. by skipping invalid data in the file). It is certainly possible to recover from encountering end of file (by ceasing to read data).

    Quote Originally Posted by Elysia View Post
    If the library only returns error codes, then the client may have to make their code more verbose by adding possible if/then/throw at every point where an I/O call is made. If the library throws an error, then the client may have to add try/catch around every non-fatal error, again making it verbose. So if the library allows for both ways of reporting the error, the client code becomes less verbose, easier to read and maintain. That's why I argue for both approaches.
    OKay, let's say an I/O library has failed to open a file named "LastResortConfig.dat", and your code has told it to throw an exception on an error. What exception should it throw?

    The IO library knows nothing about your application, since it is written to be agnostic of applications (and usable in a wide range of different applications). The only information it has is that you have told it to throw an exception, that an operation to open a file has failed, and the name of the file. At most, it might have information about why the opening failed (e.g. file does not exist, the file is protected so it can't be opened, etc).

    That means, at most, it can only throw an exception indicating an IO error has occurred, maybe what specific IO error has occurred, and supporting information (if available) about contributing factors. So it throws an exception type to report such information. That exception propagates up (the call stack) to the code that requested opening the file "LastResortConfig.dat". Presumably that code is aware that this is a last resort action. So what does that code do?

    So let's look at the three choices;
    • Swallow the exception (i.e. catch it and don't throw something else). This might include simply returning an error code. This certainly avoids the need for the application to recover at a higher level, but also provides no recovery from the cause. Not a good choice for a critical error. If we're doing this, why are we telling the library to throw an exception?
    • Translate the exception. For example, catch our low level IOError and throw CriticalConfigError, possibly including the required information. The same could have been achieved, more easily, with an error code (wrapping code in a try/catch, translating a caught exception in a catch block is more involved than "if (....) throw CriticalConfigError(available_info)".
    • Allow the exception to pass through. This means the higher level code will need to catch and make sense of the exception from the IO library (i.e. that we've failed to open a file named "LastResortConfig.dat"). That means the top level of the application will need to be written with specific information about what that filename means, what failure to open it means to the application. Every time we add some low level code that attempts another fallback to finding a valid config, we need to rewrite the top level handler to deal with the specific low level exceptions that might get emitted. Yes, it allows your code that requested opening of "LastResortConfig.dat" to be nice and clean, but leaves a mess for the application itself to sort out.


    Yes, there are ways around this, but they make the IO library more difficult to use in other ways (e.g. having to supply additional arguments to most functions, specifying HOW to throw an exception). That is essentially the same as providing callbacks from the IO library into client code that throws exceptions. So, if you want the IO library to throw exceptions relevant to the application (rather than itself) there is a detailed setup required for every operation. That's a big price to pay so that your code which requests some file operations can be written cleanly.



    The basic concern I see with your approach is that it doesn't scale. It is best suited to an IO library being used by a single application layer. Larger scale applications have multiple layers. Throwing exceptions at the bottom layers (which is where the IO library would often fit, in practice) forces all layers above to either handle low level exceptions (leaving the mess to be sorted out at the top level). Yes - every layer below looks clean, but the application itself is sensitive to everything the low level layers do (and therefore fragile). Translating exceptions for passing to a higher level negates the benefits you claim.
    Last edited by grumpy; 01-23-2015 at 07:34 PM.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  13. #13
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    [Edit]
    TL;DR: Neither the exception mechanism or returning error codes recovers from erroneous state; neither approach changes where the recovery code needs to live in order to successfully handle a problem. The difference is that the C++ language can transparently propagate an exception to the recovery code which, yes, will be much the same for both types.
    [/Edit]

    OKay, let's say an I/O library has failed to open a file named "LastResortConfig.dat", and your code has told it to throw an exception on an error. What exception should it throw?
    O_o

    So what does that code do?
    Two people can play the "Post Nonsense at CBoard" game.

    Let's say library functions fails, your code has requested an error value to be returned.

    So what does your function do with an error code? Let's look at the three choices:

    1): Ignore the error. Ignoring the error avoids the need for the application to recover. Wait. If we don't respond to an error, why the hell are we allowed near a compiler?

    2): Translate the error. Translating the error allows us to be more specific about the error than the library we are using. Of course, we haven't actually responded to the error; we are expecting calling functions to respond to the error code we return. So what does the calling functions do with an error code? Yay. I love recursion!

    3): We don't know what to do with the error code so we just `return' whatever we got from the library. Responding gracefully in the face of failure is "Someone Else's Problem". If I wanted to solve other people's problems, I'd be a marriage counselor.

    That's a big price to pay so that your code which requests some file operations can be written cleanly.
    Writing code that takes errors seriously is a lot of work regardless of how you report the context. If you are going to gracefully handle the failures of a primitive, you have to do the work. If you aren't going to gracefully handle the failures of a primitive, why are we even talking? You don't need error code or exceptions; you can just let the application crash.

    Let's take a serious look at the cost of an exception throwing agent using good C++ idioms.

    Code:
    FileReader s("filename.ext", MyAgent);
    Done. We've just added an extra argument to the constructor.

    Is that a big price to pay? Let's look at the `MyAgent' functions just in case I've hidden a burden.

    Code:
    void MyAgent
    (
        int fError
    )
    {    switch(fError)
        {
            case /* ... */: {
                throw Whatever1(/* ... */);
            } break;
            case /* ... */: {
                throw Whatever2(/* ... */);
            } break;
            case /* ... */: {
                throw Whatever3(/* ... */);
            } break;
        }
    }
    We've translated an error code into an exception. Wait.

    So, if you want the IO library to throw exceptions relevant to the application (rather than itself) there is a detailed setup required for every operation.
    Well. We now know for a fact your "detailed setup required for every operation" isn't remotely true.

    The basic concern I see with your approach is that it doesn't scale.
    A bit of code that internally translates error codes into exceptions doesn't scale? Are you sure?

    Code:
    FileReader sIn("filename.ext", MyAgent);
    
    int sId;
    string sName;
    
    sIn >> sId >> sName;
    Looks fine to me, but we want to be fair so let us take a look at responding to the returned error code.

    Code:
    FileReader sIn("filename.ext");
    
    int sError;
    
    int sId;
    string sName;
    
    sError = (sIn >> sId);
    if(sError)
    {
        MyAgent(sError);
    }
    sError = (sIn >> sName);
    if(sError)
    {
        MyAgent(sError);
    }
    Wait! The agent doesn't have to raise an exception. We can allow `MyAgent' to do nothing if the error code is clean.

    Code:
    FileReader sIn("filename.ext");
    
    int sId;
    string sName;
    
    MyAgent(sIn >> sId);
    MyAgent(sIn >> sName);
    Wait. We have operator overloading! The `MyAgent' function raises an exception to report an error so we can use the return value to facilitate chaining!

    Code:
    FileReader sIn("filename.ext");
    
    int sError;
    
    int sId;
    string sName;
    
    MyAgent(MyAgent(sIn >> sId) >> sName);
    Wait! Now we've just applied an algorithm to the result an operation while returning the same in the case of successful execution. We might need to respond with different exceptions at some later date, and we don't like to repeat ourselves. Let's factor the separate goals so we can compose the functions from whatever agent happens to be useful to the client.

    Code:
    FileReader sIn("filename.ext");
    
    int sError;
    
    int sId;
    string sName;
    
    Chain(Chain(sIn >> sId, MyAgent) >> sName, MyAgent);
    I am so silly. I keep forgetting that we are talking about the C++ language. The C++ language has classes an operator overloading. We can just use those tools.

    Code:
    struct MyFileReader
    {
        // ...
        template
        <
            typename F
        >
        MyFileReader
        (
            const char * fName
          , F f
        ):
            m(fName)
        {
        }
        // ...
        FileReader m;
    };
    
    // ...
    
    MyFileReader sIn("filename.ext", MyAgent);
    
    int sError;
    
    int sId;
    string sName;
    
    sIn >> sId >> sName;
    Sweet. Now we have the boilerplate response to error conditions isolated behind an interface so our client code is clean.

    o_O

    Code:
    sIn >> sId >> sName;
    Code:
    sIn >> sId >> sName;
    $%*@

    *shrug*

    Well, we still have the issue of how to respond to the exceptions.

    ^_^

    I love recursion!

    Soma
    Last edited by phantomotap; 01-23-2015 at 09:36 PM.
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  14. #14
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by phantomotap View Post
    Two people can play the "Post Nonsense at CBoard" game.
    Given that is your starting point for your post, I haven't bothered to read further, so have no idea if there is any merit in your post or not.

    Not to matter. I've been tiring of this site anyway, and your opening line here is just the proverbial straw that has broken the camel's back. Goodbye. I won't be logging in again, and I won't be back on this site again.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  15. #15
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by grumpy View Post
    Not to matter. I've been tiring of this site anyway, and your opening line here is just the proverbial straw that has broken the camel's back. Goodbye. I won't be logging in again, and I won't be back on this site again.
    Well, that's a shame. but I'll clarify my point anyway so that others may benefit from the answer.

    Quote Originally Posted by grumpy View Post
    OKay, let's say an I/O library has failed to open a file named "LastResortConfig.dat", and your code has told it to throw an exception on an error. What exception should it throw?
    Obviously it throws some internal exception, but that's not the point. We're not going to allow that to propagate.

    The bottom point is that no matter what you do, you're going to have to add some boiler plate code to handle the error or translate it into some other exception and re-throw it. But the point is how that is done.
    Scenario 1. The library returns an error. The client code treats any error returned by the library as an error that cannot be recovered from:

    if (MyIoOp() != OK)
    throw MyExcetion();

    You could try to refactor this into a function, and you would get:

    CheckOk( MyIoOp() );

    This is repeated for every IO op:

    CheckOk( MyIoOp() );
    CheckOk( MyIoOp() );
    CheckOk( MyIoOp() );
    //...

    Scenario 2. The library throws an exception. The client code treats any error returned by the library as an error that cannot be recovered from:

    Code:
    try
    {
        MyIoOp();
        MyIoOp();
        MyIoOp();
    }
    catch (Exception)
    {
        throw MyException();
    }
    The difference is now that the error handling has been moved to the catch handler instead of having to write verbose code for every line to check for errors.

    Scenario 3. The library throws returns an error. The client code treats this error returned by the library as an error that can be ignored:

    MyIoOp();
    MyIoOp();
    MyIoOp();
    //...

    No error handling, because we're sure that regardless if it succeeds or fails, we don't care!
    Scenario 4. The library throws an exception. The client code treats this error returned by the library as an error that can be ignored:

    Code:
    try { MyIoOp(); } catch (...) {}
    //...
    And so we repeat for every line. Horrible.
    We can use a macro or function with a lambda to deal with that, which makes it a little bit better, but still verbose:

    IGNORE_ERRORS( MyIoOp() );
    // or...
    Code:
    IgnoreErrors([]{ MyIoOp(); });
    Scenario 5. The library returns an error. The client code must locally check this for errors since different errors must be handled differently:

    if (OpenFile() != OK) CreateFile();
    if (ReadFile() != OK) /* Recover */
    // ...

    Sometimes we can use helper functions such as CanOpenFile which is used to query things and do not fail, hence returning a status flag. That's nice, but it's not always possible. It also makes the code more verbose, because we have to check if it's possible, then do it. Sometimes it can still fail (e.g. races), so we have to be prepared OpenFile can STILL fail, even though CanOpenFile returned true. In these cases, we can also get extra overhead because OpenFile may have to check CanOpenFile before it attempts to open the file (i.e. wide contracts).

    Scenario 6. The library throws an exception. The client code must locally check this for errors since different errors must be handled differently:

    Code:
    try { OpenFile(); } catch (Exception) { CreateFile(); }
    try { ReadFile(); } catch (Exception) { /* Recover */ }
    //...
    The diference here is that we potentially have many try/catch statements and not just a single global one that covers all exceptions.
    If we make our I/O library return errors, we can scenario 1, 3 and 5. If it throws an exception, we get scenarios 2, 4 and 6. But that's verbose. We wants scenarios 2, 3 and 5. How do we do this? By enabling the library to conditionally throw an exception.
    It doesn't change the fact that we have to handle the error. It changes where we handle the error by making the code less verbose.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. throw in function name
    By l2u in forum C++ Programming
    Replies: 10
    Last Post: 07-28-2008, 12:37 PM
  2. Throw away the key!
    By Salem in forum A Brief History of Cprogramming.com
    Replies: 4
    Last Post: 06-12-2008, 08:28 PM
  3. Why does C++ need throw()
    By meili100 in forum C++ Programming
    Replies: 19
    Last Post: 11-10-2007, 12:34 PM
  4. function throw
    By l2u in forum C++ Programming
    Replies: 3
    Last Post: 04-30-2007, 08:09 AM
  5. re-throw an exception
    By filler_bunny in forum C++ Programming
    Replies: 7
    Last Post: 10-15-2003, 06:06 AM