Thread: throw underflow?

  1. #16
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    This thread may be too much for a newbie without additional context.

    I think that the design questions being discussed are extremely important.

    I've provided some source that may give a newbie enough context to follow along without necessarily understanding the finer points.

    The small details, such markup as names and layout, are optional of course; it is the concepts shown that are center to the discussion.

    Soma

    Code:
    // The preferred approach whenever possible.
    double Calculator::run()
    {
        // ...
            switch(lOperation)
            {
                // ...
                case Add:
                {
                    // ...
                    // We are going to perform the addition operation.
                    // The addition operation is a binary operation.
                    // We know we need two values in the stack.
                    // We know that the operation can't fail if the precondition is valid.
                    // We have a way to get the size of the stack. (Precondition.)
                    // ...
                    if(2 <= lValues.size())
                    {
                        double lLHS(lValues.top());
                        lValues.pop();
                        double lRHS(lValues.top());
                        lValues.pop();
                        // ...
                    }
                    // ...
                }
                // ...
            }
        // ...
    }
    Code:
    // This is not as clean as the above, but it is usually possible.
    double Calculator::run()
    {
        // ...
            switch(lOperation)
            {
                // ...
                case Add:
                {
                    // ...
                    // We are going to perform the addition operation.
                    // The addition operation is a binary operation.
                    // We know we need two values in the stack.
                    // We know that the operation can't fail if the precondition is valid.
                    // We have no way of knowing how many items are in the stack.
                    // We can check if the stack is empty. (Precondition.)
                    // ...
                    double lLHS(0.0);
                    double lRHS(0.0);
                    if(!lValues.empty())
                    {
                        lLHS = lValues.top();
                        lValues.pop();
                        if(!lValues.empty())
                        {
                            lRHS = lValues.top();
                            lValues.pop();
                            // ...
                        }
                        else
                        {
                            // ...
                            // We have to deal with the error in some fashion.
                            // ...
                        }
                    }
                    // ...
                    // ...
                }
                // ...
            }
        // ...
    }
    Code:
    // This is a totally unacceptable approach which no one is advocating and included only for completeness.
    double Calculator::run()
    {
        // ...
            switch(lOperation)
            {
                // ...
                case Add:
                {
                    // ...
                    // We are going to perform the addition operation.
                    // The addition operation is a binary operation.
                    // We know we need two values in the stack.
                    // We know that the operation can't fail if the precondition is valid.
                    // We have no way of knowing how many items are in the stack.
                    // We will not check if the stack is empty for this example. (Precondition.)
                    // We know that the operation does nothing in the face of a precondition violation.
                    // We know that the operation does not do any notification in the face of a precondition violation.
                    // ...
                    double lLHS(0.0);
                    double lRHS(0.0);
                    lLHS = lValues.top();
                    lValues.pop();
                    lRHS = lValues.top();
                    lValues.pop();
                    // ...
                    // We have no way of knowing if an error occurred.
                    // ...
                }
                // ...
            }
        // ...
    }
    Code:
    // The approach centered around returning a boolean value.
    double Calculator::run()
    {
        // ...
            switch(lOperation)
            {
                // ...
                case Add:
                {
                    // ...
                    // We are going to perform the addition operation.
                    // The addition operation is a binary operation.
                    // We know we need two values in the stack.
                    // We know that the operation can't fail if the precondition is valid.
                    // We have no way of knowing how many items are in the stack.
                    // We will not check if the stack is empty for this example. (Precondition.)
                    // We know that the operation does nothing in the face of a precondition violation.
                    // We know that the operation does notification in the face of a precondition violation.
                    // ...
                    double lLHS(0.0);
                    double lRHS(0.0);
                    lLHS = lValues.top();
                    if(lValues.pop())
                    {
                        lRHS = lValues.top();
                        if(lValues.pop())
                        {
                            // ...
                        }
                        else
                        {
                            // ...
                            // We have to deal with the error in some fashion.
                            // ...
                        }
                    }
                }
                // ...
            }
        // ...
    }
    Code:
    // The approach centered around raising an exception.
    double Calculator::run()
    {
        // ...
            switch(lOperation)
            {
                // ...
                case Add:
                {
                    // ...
                    // We are going to perform the addition operation.
                    // The addition operation is a binary operation.
                    // We know we need two values in the stack.
                    // We know that the operation can't fail if the precondition is valid.
                    // We have no way of knowing how many items are in the stack.
                    // We will not check if the stack is empty for this example. (Precondition.)
                    // We know that the operation raises an exception in the face of a precondition violation.
                    // ...
                    double lLHS(lValues.top());
                    lValues.pop();
                    double lRHS(lValues.top()); // We know the `pop' operation was successful or we wouldn't have gotten to this point in code.
                    lValues.pop();
                    //
                    // We know the `pop' operation was successful or we wouldn't have gotten to this point in code.
                    // ...
                }
                // ...
            }
        // ...
    }
    
    // ...
    void Calculator::apply
    (
        const char * fExpression
    )
    {
        // ...
        try
        {
            // ...
            run();
            // ...
        }
        catch(...)
        {
            // ...
            // We have to handle the error in some fashion.
            // ...
        }
        // ...
    }
    // ...
    Last edited by phantomotap; 10-07-2011 at 08:02 PM.

  2. #17
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    You never need to use `try' and `catch' blocks unless you are going to do something with the exception.
    In that case why throw at all? I do see your point that not popping internally in the library is babysitting. With that I would most likely allow the user to do so with the idea that the user of the library should know how to use the stack. So if you request the stack to pop when it is clearly empty it should most likely crash since that is exactly what the client code told it to do.

    If you throw you then force the client to wrap every pop in a try / catch which is far uglier than a simple if.
    O_o

    Well, I wasn't going to respond to this, but let me go ahead and smack that notion out of you.

    Such a notion is simply false; I don't know where you got the notion, but you need to relieve yourself of that ignorance as soon as is possible.
    We are simply discussing design here and it isn't even our code so there is no need to get testy. I'm not even sure there is one right answer. If you will read my post I said that all three options are perfectly valid in their own right and it eventually comes down to a design decision. I am certainly not trying to convince here but I am arguing a point or two. Most likely I lean more towards doing exactly what the client code asks even if the client code is wrong. But this would also depend on what type of system it was going into and what that system was going to control. If I were writing some super generic library like the STL then I would probably take the design approach that some babysitting was necessary since one would never know the application of the code and/or what it would be controlling.

    As to the try / catch the point is if 99 times out of 100 no one is going to wrap it then why throw at all? Doing the operation will yield the same result as not wrapping it in a try / catch. In both cases the program will come down. So if the client has to think about when to handle the exception could they also just think about how they are using the stack and not request it to pop when it was empty? For me I feel the responsiblity of the usage of the library falls on the client.

    So out of the three or four options we have discusses I vote for either doing nothing (which as you pointed out is some measure of babysitting) or for going ahead and performing the operation just as the client code asked and thus bring the program down. I do not like the exception approach but if it does get thrown and is not caught it will also bring the system down so the result is the same. The major difference I guess is that with a throw you are at least providing the opportunity for the client to gracefully recover and they would not have that option if the operation was performed as requested and crashed the program.

    I'm also used to working in a DLL based environment where every piece and component is another DLL. Throwing across the DLL boundary is not a good idea if your component is going to be in the field for awhile which pretty much guarantees that the compiler used to build the DLL is not the same compiler used to build the client and thus could cause major issues.

    I would argue the boolean return is probably the best and worst of both worlds and would also point to what you said about exceptions. If you are not going to do anything with the return value then you won't be testing for it in most of the code which means you won't be wrapping every pop() inside of an if just like you would not wrap every pop() inside of a try / catch. The boolean return would at least inform the user that the call failed or did nothing and the library code could then do nothing which means it can both notify the user and not perform the operation. The big problem I find when writing library code is that 9 times out of 10 the client code never checks the return value and so the library crashes anyways. But again if the client code has to make a conscious decision to wrap the pop() in an if or in a try / catch....they could just as well make the conscious decision to use the library correctly. So I guess I would like to ask you a simple question. When would the client wrap the pop() in a try / catch or in a conditional if there is the possibility the call could fail. If there is the possibility the call could fail then should not the return value and/or the exception be checked for each time? You act as if the client code is going to magically know when the call could fail...or know when the stack is empty...and if they knew that....then why the heck would they call pop() in the first place?

    Most libraries and COM components that I have used use return values from the function which indicate failure or success and internally do not perform operations that will crash the program. Note that I said most as there are some that take a different approach and simply crash the program.

    This thread may be too much for a newbie without additional context.
    Agreed. Although I think design decisions such as this one are extremely important and worth discussing. It is my opinion that the design of something like this is far more important than the details of the implementation since the implementation is pretty straightforward.
    Last edited by VirtualAce; 10-07-2011 at 10:56 PM.

  3. #18
    Registered User
    Join Date
    May 2011
    Location
    Around 8.3 light-minutes from the Sun
    Posts
    1,949
    Wow, a lot of very interesting topics expressed here. Back to the actual question:
    Quote Originally Posted by stefanyco View Post
    So let's say we're using a stack that fluctuates, as in infix to postfix expression conversions.
    Why, are you using an explicit stack for this? This can easily be handled via a series of function calls, as in most constructs of this type. What topic are you trying to learn?

    Quote Originally Posted by stefanyco View Post
    At any given moment the stack may be empty, and if so it would abort because of the underthrow? What would be a good alternative?
    Well, that depends on how you have setup your program. Where is it that you use your try-catch block? Do you even understand how they work?
    Quote Originally Posted by anduril462 View Post
    Now, please, for the love of all things good and holy, think about what you're doing! Don't just run around willy-nilly, coding like a drunk two-year-old....
    Quote Originally Posted by quzah View Post
    ..... Just don't be surprised when I say you aren't using standard C anymore, and as such,are off in your own little universe that I will completely disregard.
    Warning: Some or all of my posted code may be non-standard and as such should not be used and in no case looked at.

  4. #19
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_o

    You clearly do not understand the value and place of exception mechanisms.

    Seriously, please go and read some articles on the design of the C++ exception mechanism and tutorials on the proper use of that mechanism.

    I don't care if you think I'm being a jerk. Do yourself the favor of relying on my expertise and learn about C++ exceptions for yourself.

    The "which approach" discussion was useful and an important topic, but repeatedly suggesting that every function invocation would need be wrapped, even if the client code would probably fail to do that, with the exception mechanism method is pointless, wrong, and distracting.

    Still, I hate to say "You are wrong; go do research." without context. Regardless of what you apparently think I respect you quite a lot. (It must be said though, I don't do "PC".) I'll go ahead and carefully respond to what you've asked, but at this point I doubt you'll read it.

    Perhaps you need to smack yourself first and change your attitude towards me before we continue discussing this.
    My attitude is that you don't know how to properly use the exception mechanism that C++ exposes and that you need to dispose yourself of that ignorance.

    It isn't a big deal; no one knows everything, but you have many times on this forum called yourself a C++ programmer. To name yourself a C++ programmer and so fundamentally misunderstand the exception mechanism is a shame.

    I wasn't being rude by the way. It, "smack that nonsense right out", is an expression.

    As to the try / catch the point is if 99 times out of 100 no one is going to wrap it then why throw at all?
    This is the exact opposite of the reason exception mechanisms in any language exists. The exception mechanism exists solely to propagate an error with any relevant context to an appropriate handler; it does not replace one local branch mechanism with another.

    Indeed, to say that "99 times out of 100" you will wrap every function invocation with `try' and `catch' blocks is to completely circumvent the exception mechanism. To make this absolutely clear, you shouldn't use any exception mechanism in that fashion; such a design is fundamentally broken.

    Furthermore, to ask "why throw at all" is horrifying; if you are going to wrap every function invocation you absolutely should not be using exceptions at all; you should not be catching anything because you should not be throwing anything if you are always going to handle the exception locally; you should be using other means to convey an error situation.

    If you will read my post I said that all three options are perfectly valid in their own right and it eventually comes down to a design decision.
    You did say that. You changed it in an edit. I started writing my reply before the edit only to be interrupted by life.

    It doesn't really matter though; my suggestion to educate yourself about C++ exceptions remains. You don't know how to put them to their best use. I understand that. I suggest you now learn how to put them to their best use as soon as is possible. They are a great feature in almost every language where they exist. (I can't tell you how pleased I was that Python was getting some improvement in that area.)

    Doing the operation will yield the same result as not wrapping it in a try / catch.
    That is more nonsense. Consider the examples from "The C++ Programming Language".

    Again, you absolutely should not be wrapping every function invocation with `try' and `catch' blocks.

    Consider the recent thread where "DirectDraw" was discussed. Your understanding of the exception mechanism as evidenced by your statements in this thread puts you at the same level as the "DirectDraw" advocate. It isn't really a matter of "point of view"; it is that you are operating on very bad information.

    The discussion of the options relating to error notification and propagation is an important one, but the fact that you don't understand the exception mechanism is a different thing entirely. I'm not saying that your opinions relating to "return value or no return value" and "do nothing or just crash" isn't worth discussing. Those are options worth consideration in any given situation. I'm saying that until you improve your understanding of exceptions your opinion of exception mechanism based method is irrelevant.

    So if the client has to think about when to handle the exception could they also just think about how they are using the stack and not request it to pop when it was empty?
    These are not mutually exclusive goals. They are, in fact, the same goal.

    The exception mechanism says "Hey! You need to know that something bad happened!". If the client code only wraps the function invocation with paraphernalia to suppress the warning, the are breaking the logic of the system do to a fundamental misunderstanding of the exception mechanism.

    It isn't a question of "If you don't catch this, I'll scream about it.". That sort of thinking is beyond foolish. It is a question of "If you don't handle the condition that resulted in this situation, I'll scream about it.". Yes, the exception mechanism does the screaming. Yes, a simple return value can do the screaming. Both of these situations tell the client code something about the situation. The return value approach must be processed locally with a branch of some sort. (Yes, even if the return value is checked only to short circuit the function the condition is being processed locally.) The exception mechanism propagates the error condition upwards, and in some cases sideways, until it finds a bit of code that can handle the situation appropriately. With the exception mechanism, the client code doesn't have to ask "Should I check this just to be sure it succeeded?" as it would be for the return value version; the client code can assume that the function invocation was successful otherwise processing would have "jumped" to the bit of code designed to handle that error condition.

    In other words, your understanding of the exception mechanism is almost entirely backwards.

    Though the advice is terse, I will offer you this bit of folkish wisdom: "Throw often; catch rarely.". As with any other advice, it can lead you wrong, but it is better than what you have.

    I'm also used to working in a DLL based environment where every piece and component is another DLL. Throwing across the DLL boundary is not a good idea if your component is going to be in the field for awhile which pretty much guarantees that the compiler used to build the DLL is not the same compiler used to build the client and thus could cause major issues.
    This is all true, but, as with the template argument you used to bring up, framed partially of false information. I know that statement ........es you off, but, like it or not, you are not looking at the entire picture.

    The longevity of a component is irrelevant. Most everyone here would like "Turbo C" to die for good and yet it lingers.

    Conventional wisdom of "Don't throw exceptions across library boundaries." wasn't formed because it is sound design advice. Unfortunately, the real world just gets in the way. Allowing exceptions to propagate across library boundaries in other languages is the usual approach. C++ developed that bit of advice because compilers just don't get along. (I know you know this part. We haven't got to the bit that makes this bit all but irrelevant in practice.) Unfortunately, it only tells half the story. Not only do compilers not agree on how to implement the exception mechanism; compiler venders can't even agree on class padding which is one of the most trivial components of user defined exceptions. Every experienced C++ programmer knows that "Visual C++" and "GCC" don't really get along, but that paring barely scratches the surface. In the real world you can't even guarantee that you'll be able to link against libraries compiled with a different version of the same compiler. That's the commercial ones! We have a few open source compilers. You can't even offer a reasonable guarantee that the same version of the same compiler itself built with the same flags for the same environment can link with C++ libraries because multiple times vendors have introduced strange default flags that break compatibility.

    So, yea, you are absolutely correct; you can't trust an exception thrown across a library boundary to be useable by client code, but you are absolutely wrong to think that situation means a lot because the truth is the API exported by the library may not be useable by client code even without exceptions, even without templates, even without function overloads, even without default values, even without virtual function, even without inheritance, and even without "RTTI" because the C++ standard specifically allows compiler venders to choose their own "Application Binary Interface" which means that any expectations of formal compatibility goes right out the window. (It should be noted that the standard allows this by lots of "implementation defined" areas.)

    It is my opinion that the design of something like this is far more important than the details of the implementation since the implementation is pretty straightforward.
    Relying on an exception mechanism isn't a implementation detail. Seriously, again not being a jerk but it is fine if you think so, if you knew as much about exception mechanisms as I do you would know exactly what I'm talking about. The instant you say "Okay, the constructor for this class may throw an exception." you place requirements on the design, not the implementation, that would not exist otherwise.

    If you could safely say "Compiler, I need you to enforce a "no exceptions" policy on everything I'm going to use in this function." you could do a lot better with the interface that what has been proposed; in such a world as that I would suggest something more "functional".

    Code:
        // ...
        Stack lData;
        // ...
        Popper lNext;
        // ...
        double lLHS(lNext);
        // ...
        double lRHS(lNext);
        // ...
    Granted, this can still be done, but if the underlying bits can't throw an exception when the preconditions are valid the above code can be trivially implemented in such a way that it literally can not fail. (Naturally, a rock could go through your window at exactly that moment and fault your processor.) "Can not fail." would be an amazingly wonderful property of a component.

    When would the client wrap the pop() in a try / catch or in a conditional if there is the possibility the call could fail.
    For the return value, if the operation may fail and the client code wants to handle that error at any point in any conceivable way the client code has no choice but to check the return value each and every time.

    For the exception mechanism, if the operation may fail the client code can not recover from such an error locally the client code may safely ignore that possibility at that point in the code; the client code does not have to process the exception and can be written almost as if no error could occur while still providing a means of graceful recovery or at least an informative crash.

    If there is the possibility the call could fail then should not the return value and/or the exception be checked for each time?
    Look at the relevant bits from the source I posted. I checked the return value every time; I have to do that if I want deal with that error condition in a safe and graceful way. However, I only check for the error condition as an exception once because all of the errors related to "Insufficient Data: The expression being operated on was invalid." can be handled in that one place.

    You act as if the client code is going to magically know when the call could fail...or know when the stack is empty...and if they knew that....then why the heck would they call pop() in the first place?
    That is simply wrong; I'm not acting or saying anything that could possibly make you think that I'm implying magic or psychic client code.

    That is very nearly the opposite of what I'm saying.

    I've said, more than once, that with the return value method, if the client code might ever care about the error condition, the return value must be inspected at every function invocation. Manually coding multiple points of inspection is, in this context, as far from being magic as is possible.

    I've also said that with the exception mechanism method the client code doesn't have to inspect the exception every invocation unless the error condition can be handled locally. That isn't magic; that is simply what an exception mechanism offers.

    Soma

  5. #20
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    stefanyco, if you are getting lost concerning the discussion on exceptions in this thread, I suggest that you beg/borrow/steal a copy of C++ Coding Standards by Sutter and Alexandrescu and read item #62 ("Don't allow exceptions to propagate across module boundaries") and the entire chapter on error handling and exceptions (items #68 to #75).
    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

  6. #21
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Thanks for fixing up the thread a little, LL.

    "Don't allow exceptions to propagate across module boundaries"
    As a matter of interest, from memory, this item goes further than just suggesting that one not allow exception to cross shared library boundaries; the item suggests, without the flowery words, that code shouldn't allow C++ exceptions to cross into or out of any unfamiliar frame regardless of origin. Though it sounds simply more general, it is actually much better advice.

    Soma

  7. #22
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    My attitude is that you don't know how to properly use the exception mechanism that C++ exposes and that you need to dispose yourself of that ignorance.

    It isn't a big deal; no one knows everything, but you have many times on this forum called yourself a C++ programmer. To name yourself a C++ programmer and so fundamentally misunderstand the exception mechanism is a shame.

    I wasn't being rude by the way. It, "smack that nonsense right out", is an expression.
    I figured it was an expression but not one I was fond of nor would I tolerate either here or in person. To question whether or not I am a C++ programmer goes a bit far in my opinion but you are free to your opinions as is so clearly evident. The shame is on you for not being able to discuss issues such as this without resorting to some personal attack on my validity as a C++ programmer.

    BTW went to Amazon and bought the book.
    Last edited by VirtualAce; 10-08-2011 at 10:37 AM.

  8. #23
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by VirtualAce
    So my final question is are not exceptions for exceptional cases where it is necessary to break out of the current context and handle the error on a higher level? What higher level is going to handle this?
    In the worst case, a last chance catch-all exception handler at the module boundary that will say, log the exception and somehow inform the module user that there is a problem likely due to a bug.

    On the same note, one might prefer to use the at member function of std::vector instead of operator[] in view that if somehow there is an out of bounds programming error, an exception will be thrown instead of invoking undefined behaviour that could allow a security exploit.

    Of course, your point here is that this is a module boundary, in which case exception propagation should end here. However, I note that we are dealing with a member function of a class template, so it could well be the case that this library was intended to be compiled along with client code, thus there would be no module boundary problem that you typically face when developing DLLs (i.e., this is part of the same module as the client code).

    So, back to your original point:
    Quote Originally Posted by VirtualAce
    I would argue that type of error does not warrant a throw.
    The type of error here is a logic error, a programming bug. Is it a must to throw an exception for this? I don't think so, e.g., we might reasonably decide to use an assertion instead. It is wrong to throw an exception for this? I don't think so either, since it avoids undefined behaviour and could provide valuable feedback to the programmer using this class template through error logs if the problem somehow makes it past his/her tests into production.
    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

  9. #24
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Sorry laserlight I edited my post b/c I felt like I was not adding anything noteworthy to the discussion. Seems the board has been slow with updating the content. I also edited my earlier post about being smacked in the face b/c I felt my first version was a bit on the harsh side and it did not take in time either. Weird thing is I refreshed the page and no new posts were there so perhaps both you and phantomatap were writing your posts as I edited mine.

    thus there would be no module boundary problem that you typically face when developing DLLs (i.e., this is part of the same module as the client code).
    Yes that would be a different scenario and would require a different approach. In that case I might be more inclined to an exception but then my argument would be in agreement with your that I still do not feel this type of error warrants an exception.
    Last edited by VirtualAce; 10-08-2011 at 11:40 AM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Stack underflow
    By homer_3 in forum C Programming
    Replies: 5
    Last Post: 09-02-2009, 01:28 PM
  2. popen and fgets, underflow!
    By henrikstolpe in forum Linux Programming
    Replies: 0
    Last Post: 02-06-2009, 03:39 AM
  3. handling overflow, underflow etc
    By broli86 in forum C Programming
    Replies: 8
    Last Post: 07-03-2008, 07:10 AM
  4. stack underflow issue
    By camo in forum C Programming
    Replies: 13
    Last Post: 10-31-2005, 06:53 AM
  5. memory underflow.
    By newbie_grg in forum C++ Programming
    Replies: 4
    Last Post: 01-26-2003, 11:19 AM