Sometimes a running function will recognize that there is no way to do its job. Perhaps it is supposed to read a file and that file does not exist. Or maybe it was given a parameter that makes no sense. To explore our options, we will use this function:
The best way to handle the bad value will depend on what the program overall is doing: Is the data user input and we should ask for a new value? Is there a log file that we should print errors to? Is the failure to determine true/false an unrecoverable error?
Whoever is writing this particular βlow-levelβ function might have no idea of what the βhigher level codeβ is doing and thus how to best handle the issue. Furthermore, it is possible the function is being used by many different programs. In that case, there is no single best way to handle the issue.
Figure10.6.1.A function like stringToBool that calls no other functions and does a small task is βlow-levelβ. A function like main that is responsible for everything is βhigh-levelβ.
Thus we need a way for a low-level function to communicate back to the caller that there is a problem. The higher-level code is the right place to decide what to do about that issue.
The first solution is not much of a solution. It is just giving up completely. An assertion is a check that if failed will cause the whole program to end. In C++ we can use include the <cassert> library and then make an assertion using assert. An assert takes a condition to check. If that condition is true, the program continues. If the condition is false, the entire program stops:
Assertions can be useful in development to make sure a program stops instead of trying to coverup an issue and continue. And they can be useful to make 100% sure a program stops in some situation where continuing on could have undesirable consequences. But they arenβt a good tool if want to have any chance to recover from the issue.
The next strategy is to return a special value that indicates an error. This is the strategy used by size_t string::find(string value). It returns the special string::npos value when the value is not found.
However, this is only possible if there is a special value that canβt possibly be a real answer. string::npos is a valid size_t. But it can never be a valid index into a string. So the caller can check for that special value and know that the search failed.
In the case of our stringToBool function, there are only two legal values to return - true or false. And each is a possible real result. So there is no possible special value that can be used to indicate βerror - I donβt know what to do.β
In addition to not always being possible, there is a more subtle issue with returning a special value. What if the caller doesnβt know how to handle the error?
For example, imagine a main() that has called bigJob() that has called mediumJob() and that has called stringToBool(). stringToBool() passes a special value back to mediumJob(). mediumJob() needs to look for that special value and decide what to do. If it doesnβt know how to respond to the issue, it would need its own special value to return to bigJob(), which might need to pass a message back to main(). Passing error values through multiple levels until we reach a level of code that knows what to do can be painful.
If we canβt return a special value, we could instead use an extra error flag to indicate if there was an issue. To do so, we would add an extra bool reference parameter:
bool stringToBool(const string& s, bool& error) {
if (s == "true") {
return true;
} else if (s == "false") {
return false;
}
error = true; // Set error to true if the string is not "true" or "false"
return false; // What do we do??????
}
Now to call the function, the caller needs to pass in a bool variable:
This method works. But it requires the extra parameter and suffers from the same issue as returning a special value. Sometimes, the caller wonβt know what to do about the error. So it would need to set an error flag for its caller, who might need to set an error flag for its caller... We have to modify the entire chain of functions so that the high-level code can get an error back from low-level code.