0

The program communicates with user through class Menu, so the main() looks like this:

int main() {
    try {
        Menu menu(/*...*/);
        while (true) {
            menu.printOptions();
            menu.chooseOption();
        }
    }
    catch (const char *error) { /*Resolve error.*/ }
    catch (int code) { /*Exit with code.*/ }
}

Current class Menu's option used for exiting is as such:

void exit() {
    // Release memory.
    throw 0;
}

Should constructs like this be avoided and do they have some unpredictable (or undesired) side-effects?

  • 2
    Using the exception mechanism as a flow of control construct is ill-advised. In this particular case, just call `std::exit()` – StoryTeller - Unslander Monica Nov 29 '16 at 11:13
  • 3
    I would generally not create a function called `exit` since this would shadow the `std::exit` function. And I would be careful exiting using the `std::exit` as @StoryTeller suggests, since it does not unwind the stack. – Tommy Andersen Nov 29 '16 at 11:22
  • 3
    @TommyAndersen: It will overload it, not shadow it. Which will be problematic only in a context where both are considered in overload resolution. I did forget about stack unwinding though. Definitely should just `return` with a status all the way up to main. – StoryTeller - Unslander Monica Nov 29 '16 at 11:30
  • 1
    @TommyAndersen - no, `exit` won't shadow `std::exit`, since `std::exit` is a different name from `exit`. That's why we have namespaces. Unless, of course, you blow away `std` with `using namespace std;`, in which case you've asked for a great deal of trouble. – Pete Becker Nov 29 '16 at 13:11
  • 1
    @StoryTeller - no, `exit` won't overload `std::exit`. They're not defined in the same scope. – Pete Becker Nov 29 '16 at 13:12
  • @PeteBecker, considering the place where it can pop up as a problem is during overload resolution, I'd say the standard needs a better name for the process itself – StoryTeller - Unslander Monica Nov 29 '16 at 13:16
  • @StoryTeller - the process of looking up names is called "name lookup". It's only when there is more than one definition of the same function name in the same scope that you have overloading and need overload resolution. – Pete Becker Nov 29 '16 at 13:19
  • @PeteBecker, I wasn't talking about name lookup, I was talking about *"overload resolution"*. The functions aren't overloads (so you said yourself). Pulling both into the same scope doesn't make them overloads, just introduces a name collision.. So "name resolution" is a better name than "overload resolution" – StoryTeller - Unslander Monica Nov 29 '16 at 13:24
  • @StoryTeller -- when name lookup encounters two names that conflict, that's the end of the lookup. There's no overload resolution involved, because there's no overloading. – Pete Becker Nov 29 '16 at 13:27
  • @PeteBecker, no, name lookup doesn't abort when two or more declarations are encountered. All the looked-up declarations are passed to the resolution stage, which is separate. – StoryTeller - Unslander Monica Nov 29 '16 at 13:29
  • @StoryTeller - sigh. I didn't say that name lookup aborts. I said that name lookup terminates when two names conflict, which obviously has to be determined by the compiler somehow. – Pete Becker Nov 29 '16 at 13:32
  • @PeteBecker -- I have the urge to sigh myself (smh). If wer'e arguing standardese, we can't just be mashing up terms together to fit our point. Either we are percise and then lookup and resolution are seperate phases, or we are not, and then it doesn't matter if the two are formally "overloads" or not when they conflict. – StoryTeller - Unslander Monica Nov 29 '16 at 13:35
  • @StoryTeller - sigh again. I didn't say that name lookup aborts, and your claim that I did is simply false. Don't get all high and mighty about precision when you misrepresent facts. – Pete Becker Nov 29 '16 at 13:37
  • @PeteBecker -- (smh... again) *" when name lookup encounters two names that conflict, that's the end of the lookup"* English may not be my native tongue, but I'm pretty sure that "ending a process" is the same as "aborting it". I misrepresented nothing about what you said. – StoryTeller - Unslander Monica Nov 29 '16 at 13:39
  • @StoryTeller -- "when name lookup encounters two names that conflict, that's the end of the lookup" is a true statement. "no, name lookup does not abort..." completely misstates what I said. Of course recognizing "names that conflict" requires more work, but that is a distraction from the point I was making. Anyway, this is far enough down this rathole. Discussion over. – Pete Becker Nov 29 '16 at 13:44
  • @PeteBecker, A misstatement, it is not. Either we are precise, or we are not. Choosing to be imprecise when bundling name lookup and overload resolution into one, after correcting one on the exact notion of an overload, is hardly the epitome of intellectual honesty. – StoryTeller - Unslander Monica Nov 29 '16 at 13:49

1 Answers1

1

I would avoid using exceptions as a mean to control flow in that way. Instead you could change your loop, from while (true) to something that checks on the state of the menu class such as while (menu.isAlive()). Exiting is a matter of simply setting the alive attribute to false, which will end the while loop.

Exceptions should be used for situations that the current flow cannot recover from, and that requires you to return control to a parent flow. Having said that, there is nothing that prevents you from using exceptions in that way, but personally I think it is bad enough to have exceptions for error handling, and thereby hidden jumps, but having them for regular flow control, is definitely something I would think polluted the code, and made it less predictable.

Exceptions have the problems that you will also find in many other articles about anti patterns, that:

  • They are similar in many ways to goto statements, in that they create "invisible" exit points in the code, that the logical program flow is broken at points which can really make it more complex to step through the code in a regular debugger.
  • Are expensive to use. More on this below.
  • Makes the code more difficult to read.
  • Is a symptom of a design problem, that should be addressed instead.

I am not against exceptions in all situations, but I do not think that all handling of "error" situations should be done using exceptions, it depends on how the "error" affects the program flow. For instance it makes great sense in situations where the error (sorry for the bad wording) is truly an exceptional state, for instance when a socket dies, or you are unable to allocate new memory, or something similar. In these cases exceptions makes sense, especially since exceptions are hard to ignore. In summary in my opinion exceptions should be used for exceptional situations.

There are other answers addressing this on SO and other Stack Exchange sites that might be of interest to you:
Are exceptions as control flow considered a serious antipattern? If so, Why?
Why not use exceptions as regular flow of control?


A note on the performance of exceptions, it is implementation specific how exception handling is done, and it may differ from compiler to compiler. Often however exceptions add no extra cost when taking the non-exceptional path, however when taking the exceptional path, there are a number of reports that suggest that the cost is indeed significant (https://stackoverflow.com/a/13836329/111143 mentions 10x/20x the cost of a regular if on the exceptional path).

Since implementations of exceptions are specific to the compiler, it can be difficult to give any general answer to this. But examining the code generated by the compiler may show what is added when using exceptions. For instance when catching exceptions you need the type information of the exception thrown, which adds additional costs to the throwing of an exception. This additional cost makes little difference when you are truly facing an exceptional state, which should happen rarely. But if used to control regular program flow it becomes a costly affair.

For instance look at the following example of a simple code that exits by throwing an integer: https://godbolt.org/g/pssysL the global variable is added to prevent the compiler from optimizing the return value out. Compare this to a simple example where the return value is used: https://godbolt.org/g/jMZhT2 the exception has about the same cost when the non-exceptional path is taken, but becomes more expensive the exceptional path is taken. Again this can be fine, if the exception is used for truly exceptional situations, but when used in situations where the "exceptional path" is hit more frequently, the cost starts to become a factor.

Community
  • 1
  • 1
Tommy Andersen
  • 6,674
  • 1
  • 26
  • 47
  • 2
    *"I think it is bad enough to have exceptions for error handling"* - Separating the control flow of the truly rare and irrecoverable errors from the more common and manageable flow is hardly a bad thing. – StoryTeller - Unslander Monica Nov 29 '16 at 11:32
  • @StoryTeller I know this is a hot subject, and really something that people have strong opinions about. Some believe as you that separating the error handling from the control flow, is a good thing (and I partly agree), while others believe that it makes the code more difficult to read, or perhaps even hides certain error handling parts (which I also agree to). But I follow your argumentation though :) – Tommy Andersen Nov 29 '16 at 11:35
  • 1
    I'm sorry, but equating them to `goto` is just plain false. Calling them expansive without profiling is a blanket statement that can be false. Your other two bullet points are more a malady of poor programmers than something inherent in exceptions. Exceptions have more than a few valid use cases, and they keep getting bad rep for over 20 years due to what is mostly superstition. – StoryTeller - Unslander Monica Nov 29 '16 at 11:40
  • 1
    A few talks [1](https://www.youtube.com/watch?v=fOV7I-nmVXw), [2](https://www.youtube.com/watch?v=Fno6suiXLPs) to counter-weigh on this – StoryTeller - Unslander Monica Nov 29 '16 at 11:45
  • `exit` does **not** overload `std::exit`. They're not defined in the same scope, so there is no overloading. – Pete Becker Nov 29 '16 at 13:14
  • 1
    Sigh. A function named `exit` does not "conflict with" `std::exit`. They have different names. That's what namespaces are for. If you blew away `std` with `using namespace std;` you deserve whatever happens to you. – Pete Becker Nov 29 '16 at 13:16
  • @PeteBecker you are right, I have removed the section. It is not relevant to the answer anyway. :) – Tommy Andersen Nov 29 '16 at 13:20