问题
Without getting into the general exceptions vs error codes discussion, what do you think are the downsides of using std::pair
or std:tuple
for returning multiple values, namely the function's return value AND the error/success code, similar to how many Go developers apparently do error handling?
This approach obviously has the advantage of not having to use out parameters for the function return value or error code (depending on which way around you prefer it).
回答1:
“what do you think are the downsides of using std::pair or std:tuple for returning multiple values, namely the function's return value AND the error/success code”
The main downside of that simplistic (C level) approach to failure handling is a
- loss of safety.
I.e. there's more that can go wrong, such as accessing an indeterminate result value. Or just using a return value when the function didn't produce a meaningful one.
The old Barton & Nackman Fallow
class solved this safety problem by restricting access to the result value. Essentially the calling code has to check if there is a result value, before using it, and using a logically non-existent result value causes an exception or termination. The boost::optional
class does much the same.
If you don't want a dependency on Boost, then an Optional
class is trivial to implement for POD result type, and at a cost of a little possible inefficiency (dynamic allocation) you can just use a std::vector
to carry a non-POD possible result.
The challenge is to retain the clarity of the calling code, which is the whole point of the exercise…
回答2:
This "idiom" is good because both the type and succes indicator are return values of the function. Failure might not be exceptional, so exceptions are inappropriate sometimes.
The downside however is that you have to split out the two return types. This can be ugly; using std::tie
helps but you are unable to construct from multiple return.
bool success;
std::string value;
std::tie(success, value)=try_my_func();
This is quite verbose.
Second, if one of the types is "optional" depending on the value of another element in the tuple then it still has to be constructed which for some types is still very wasteful.
If you are using the idiom a lot, consider instead using something like the boost::optional
type. This is close to the haskel maybe than go's multiple return.
Reference
http://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html
回答3:
For this purpose, in most cases I use an own wrapper type which introduces some syntactic sugar. Let's see an example:
template <class T>
struct Result
{
public:
enum Status {
Success,
Error
};
// Feel free to change the default behavior... I use implicit
// constructors for type T for syntactic sugar in return statements.
Result(T resultValue) : s(Success), v(resultValue) {}
explicit Result(Status status, std::string errMsg = std::string()) : s(status), v(), errMsg(errMsg) {}
Result() : s(Error), v() {} // Error without message
// Explicit error with message
static Result error(std::string errMsg) { return Result(Error, errMsg); }
// Implicit conversion to type T
operator T() const { return v; }
// Explicit conversion to type T
T value() const { return v; }
Status status() const { return s; }
bool isError() const { return s == Error; }
bool isSuccessful() const { return s == Success; }
std::string errorMessage() const { return errMsg; }
private:
T v;
Status s;
// if you want to provide error messages:
std::string errMsg;
};
Then, simply use this class as the return value in your methods which can return errors:
Result<int> fac(int n) {
if(n < 0)
return Result<int>::error("n has to be greater or equal zero!");
if(n == 0)
return 1;
if(n > 0)
return n * fac(n-1); // gets automatically converted to int
}
Of course this implementation of the factorial function is horrible, but demonstrates the conversion without bothering about the error-extended return type we use.
Example usage:
int main() {
for(int i = -3; i < 4; ++i)
{
Result<int> r = fac(i);
std::cout << i << " | ";
std::cout << (r.isSuccessful() ? "ok" : "error") << " | ";
if(r.isSuccessful())
std::cout << r.value();
else
std::cout << r.errorMessage();
std::cout << std::endl;
}
}
Output:
-3 | error | n has to be greater or equal zero!
-2 | error | n has to be greater or equal zero!
-1 | error | n has to be greater or equal zero!
0 | ok | 1
1 | ok | 1
2 | ok | 2
3 | ok | 6
One big advantage of the custom type is that you can insert some control ensuring that the client code always checks for errors before accessing the actual value and only accesses the value if it was successful respectively the error message if it wasn't. For this, we can extend the class by the following:
struct Result
{
public:
// in all constructors, add:
Result(...) : ..., checked(false) {...}
// in the error checker methods, add: (and drop const-ness)
bool is...() { checked = true; return ... }
// rewrite the value conversion as follows:
operator T() const { std::assert(checked && isSuccessful()); return v; }
T value() const { std::assert(checked && isSuccessful()); return v; }
// rewrite the errorMessage-getter as follows:
std::string errorMessage() const { std::assert(checked && isError()); return errMsg; }
private:
...
bool checked;
};
You might want to make the class definition depending on the build mode (debug build / release build).
Note that the example has to be rewritten as follows:
Result<int> fac(int n) {
if(n < 0)
return Result<int>::error("n has to be greater or equal zero!");
if(n == 0)
return 1;
if(n > 0) {
Result<int> r = fac(n - 1);
if(r.isError()) return r; // propagate error (similar to exceptions)
return n * r; // r gets automatically converted to int
}
}
The main-code from above is still valid, as it already did error-checking before accessing the value / the error message.
回答4:
It's better than regular error codes because you don't have to waste your life with out parameters. But it still retains all of the very serious downsides.
Really, it's just a micro change, it's still error codes- there's no significant benefit.
来源:https://stackoverflow.com/questions/13777987/c-error-handling-downside-of-using-stdpair-or-stdtuple-for-returning-err