Reputation: 40
So, I'm kinda new to C++ and I wanted to know what are the good practices or even how do I handle runtime errors when programming, here is an example:
State s_toState(std::string state){
if (state == "MG")
return State::MG;
else if (state == "PR")
return State::PR;
else if (state == "SP")
return State::SP;
else if (state == "SC")
return State::SC;
else if (state == "RJ")
return State::RJ;
else if (state == "RN")
return State::RN;
else if (state == "RS")
return State::RS;
// ???
}
So I have this function that transforms a string
into a State
. Without using exception, what is the ideal way for me to assert that the given state is an existing one (MG, PR, SP, etc...)?
Gave an example but I'm asking for the general rule. As far as I know i could use exceptions, assertions or just print the error. I do pretend to unit test this too (also new to unit testing and know nothing about it).
Upvotes: 0
Views: 1054
Reputation: 36637
Generally speaking, the way to handle such errors (like any errors) depends on the needs of your program as a whole - and you have not specified that. So there is no one-size-fits-all "general rule".
There are options and trade-offs.
One option is for your State
enumerated type to provide an enumerator value that represents undetermined or an invalid state, such as
enum class State {MG, PR, Undetermined};
Then, in your function, return the undetermined value, e.g.
State s_toState(const std::string &state)
{
State return_value = State::Undetermined;
if (state == "MG")
return_value = State::MG;
else if (state == "PR")
return_value = State::PR;
// etc
return return_value;
}
With this approach the function always returns a valid value of type State
. If the error conditions aren't critical (i.e. the program can continue if an invalid string is supplied) then the caller can decide if it needs to check the return value. Multiple types of error condition can be reported (e.g. by having multiple enum values representing different errors) A down-side is that the caller may forget to check the return value and behave incorrectly.
Another option is to throw an exception, for example;
State s_toState(const std::string &state)
{
if (state == "MG")
return State::MG;
else if (state == "PR")
return State::PR;
// etc
throw std::invalid_argument("Bad input string");
}
This option requires some sensible choice of what type of exception to throw (e.g. what information needs to be conveyed about the error state). This approach may be preferable if the caller (or the program as a whole) cannot sensibly continue if a bad string is provided since, by throwing an exception, the caller is forced to catch and take any recovery action to avoid being terminated. As such, this approach may not be suitable for non-critical errors - for example, if execution can sensibly continue if a bad string is provided.
Another option (C++17 and later) is to return std::optional<State>
. This allows the caller to check if an error has occurred (e.g. std::option::has_value()
return false
) occurs or, if the value is accessed without checking, cause an exception of type std::bad_optional_access
to be thrown (which may be suitable for the caller, or may be uninformative).
It's also possible to use assert()
- which forces termination if a specified condition is untrue. Generally, I prefer throwing an exception over using assert()
but you may prefer otherwise.
Upvotes: 0
Reputation: 19223
The answer depends on what s_toState
"promises" to do.
If state
being invalid is a fault of a programmer who the function or an error in other internal logic. Add assert
and document state validity as a precondition (same as !=nullptr
for pointers). For release build, add some default behaviour just so the program does not crash if possible. Or let it crash if you are not writing critical SW, it will at least make debugging easier.
If it is a recoverable fault of an user and plausible scenario (not a bug), then consider returning std::optional
or throw
. I prefer the former when the thrown exception would have always been caught by the direct caller, i.e. never propagated up the call stack. I also find it more clear as it forces the caller to explicitly handle it.
For unrecoverable faults, i.e. when the direct caller cannot handle the nullopt
, just throw and let some other code deal with that.
I like the naming convetion of try_parse
returning std::optional
, while parse
throwing.
It might be worth givin Exceptions and Error Handling FAQ a try.
Upvotes: 0
Reputation: 31
This looks like a good opportunity to use exceptions. The cplusplus.com guide is reasonable, but I found that playing around with it is a better way to learn.
The basic idea is this:
throw
s an exception, terminating the function and passing the exception to whoever called the function.try
block, then the subsequent catch
will be executed.try
/catch
system, the caller is terminated as well and the process repeats down the function call stack until it either finds a try
/catch
or main()
is terminated.Upvotes: 1