51

I recently saw that the boost program_options library throws a logic_error if the command-line input was un-parsable. That challenged my assumptions about logic_error vs. runtime_error.

I assumed that logic errors (logic_error and its derived classes) were problems that resulted from internal failures to adhere to program invariants, often in the form of illegal arguments to internal API's. In that sense they are largely equivalent to ASSERT's, but meant to be used in released code (unlike ASSERT's which are not usually compiled into released code.) They are useful in situations where it is infeasible to integrate separate software components in debug/test builds or the consequences of a failure are such that it is important to give runtime feedback about the invalid invariant condition to the user.

Similarly, I thought that runtime_errors resulted exclusively from runtime conditions outside of the control of the programmer: I/O errors, invalid user input, etc.

However, program_options is obviously heavily (primarily?) used as a means of parsing end-user input, so under my mental model it certainly should throw a runtime_error in the case of bad input.

Where am I going wrong? Do you agree with the boost model of exception typing?

Null
  • 1,950
  • 9
  • 30
  • 33
David Gladfelter
  • 4,175
  • 2
  • 25
  • 25

5 Answers5

40

In this case, I think (at least for the most part) you're right and it's wrong. The standard describes logic_error as:

The class logic_error defines the type of objects thrown as exceptions to report errors presumably detectable before the program executes, such as violations of logical preconditions or class invariants.

A command line argument that can't be parsed doesn't seem to fit that very well.

By contrast, it describes runtime_error as:

The class runtime_error defines the type of objects thrown as exceptions to report errors presumably detectable only when the program executes.

That seems to be a better fit.

Null
  • 1,950
  • 9
  • 30
  • 33
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • My key phrase here is `detectable only`. If the error was induced by an external input beyond your control, then it is `runtime_error`. Relatively speaking, the required settings variables during initialization is within your control. – daparic Jul 31 '20 at 13:14
9

From a pure standard point of view, you are right. program_options should throw classes derived from either runtime_error or logic_error depending on whether the error is runtime or logical. (I did not review the current code to determine such idea classification for current exceptions).

From a practical standpoint, I have yet to see C++ code that makes useful decisions based on whether exception is logic_error or runtime_error. After all, the only reason you would throw a logic_error as opposed to letting assert file is if you want to try recover somehow, and that's not different from recovery from a runtime error. Personally, I view logic_error vs. runtime_error the same way as checked exceptions in Java -- theoretically nice, but not useful in practice. Which means, that maybe, I'll just make program_options::error derive from exception. That is, when I'll find that 'spare time' everybody keeps talking about.

Null
  • 1,950
  • 9
  • 30
  • 33
Vladimir Prus
  • 4,600
  • 22
  • 31
  • 6
    I believe that you should let the logic_error go and terminate the program (so that the victim can then send the debug/core information to developers), while runtime_error should be catched and report: "you gave me un-parsable information, please try again." – Antoine Pelisse Sep 21 '13 at 10:41
8

The current draft of the C++0x Standard says (clause 19.2):

1) In the error model reflected in these classes (i.e. the exception types), errors are divided into two broad categories: logic errors and runtime errors.

2) The distinguishing characteristic of logic errors is that they are due to errors in the internal logic of the program. In theory, they are preventable.

3) By contrast, runtime errors are due to events beyond the scope of the program. They cannot be easily predicted in advance.

Together with the quotes cited in one of the other answers this explains why Boost.ProgramOptions throws a std::logic_error for preventable errors caused by an 'error presumably detectable before the program executes'.

hkaiser
  • 11,403
  • 1
  • 30
  • 35
  • 1
    By your logic, if the user passed in an argument containing a path to a non-existent file, the application should throw a logic error in response to the file-not-found runtime error since the argument to the application, created before the application started, was provably incorrect. I don't buy that. – David Gladfelter May 27 '10 at 22:00
  • Well, actually it's not my logic. I gave you quotes from the Standard. Obviously, there is no sharp destinction possible between both types of errors, and wrong user input is one of those 'gray zones'. – hkaiser May 28 '10 at 02:28
  • This would pivot on whether the program can be reasonably expected to check/sanitize the relevant type of input. If such an expectation is unreasonable, it is beyond the scope of the program. But the "scope" is defined as the library code's expectations, not your program's design. – Grault Sep 04 '12 at 00:20
  • 2
    I think by “error presumably detectable before the program executes” what's intended is “detectable by sufficiently carefully reading the program source code”. – John Marshall Aug 08 '17 at 09:53
0

The user could verify that the file exists, run the program, and suddenly learn that the file has been deleted. That's why an I/O problem is always a runtime_error. Stateful problems are runtime_errors, even if the caller could have checked, since another thread could cause the problem.

logic_error is when the arguments to a function are wrong. It is only for things which could have been caught earlier with stronger type-checking.

So, "missing file" is a runtime_error, but "improperly formatted filename" is a logic_error.

Technically, a logical error within a function is a logic_error too, but if you're smart enough to test for it inside your own function, you should be smart enough to prevent it in the first place.

cdunn2001
  • 17,657
  • 8
  • 55
  • 45
  • "if you're smart enough to test for it inside your own function, you should be smart enough to prevent it in the first place" - this makes a lot of assumptions, including that you have control over all the code – minexew Jul 05 '16 at 23:28
  • There are 3 domains: 1) the caller of your function, 2) your function, and 3) the libraries called by your function. If (1) calls (2) with bad input, (2) should throw `logic_error`. If something went wrong that (1) could not test for (e.g. a file is suddenly deleted by another process), (2) should throw `runtime`. That's clear, right? But what happens if (2) calls (3) improperly? That's a logical error caused by (2), so (3) could throw `logic`. I'm saying if (2) traps a `logic` thrown by (3), (2) should re-throw as a `runtime`, but it's better not to cause the error in the first place. – cdunn2001 Jul 06 '16 at 22:14
0

IMO,

  • std::logic_error is thrown by a user C++ program logic intentionally. Predicted by a user program.

  • std::runtime_error is thrown by a C++ runtime (or core part of the langauge…) to abstract lower level errors. Just happens without any intention without involving of any user code.

eonil
  • 83,476
  • 81
  • 317
  • 516