在不讨论一般异常与错误代码的情况下,您认为使用std::pair
或std:tuple
返回多个值的缺点是什么,即函数的返回值和错误/成功代码,类似于有多少Go 开发人员显然在进行错误处理?
这种方法显然具有不必为函数返回值或错误代码使用参数的优点(取决于您喜欢哪种方式)。
在不讨论一般异常与错误代码的情况下,您认为使用std::pair
或std:tuple
返回多个值的缺点是什么,即函数的返回值和错误/成功代码,类似于有多少Go 开发人员显然在进行错误处理?
这种方法显然具有不必为函数返回值或错误代码使用参数的优点(取决于您喜欢哪种方式)。
这个“成语”很好,因为类型和成功指示符都是函数的返回值。失败可能不是例外,所以例外有时是不合适的。
然而,缺点是您必须拆分两种返回类型。这可能很难看;使用std::tie
帮助,但您无法从多个返回中构建。
bool success;
std::string value;
std::tie(success, value)=try_my_func();
这是相当冗长的。
其次,如果其中一种类型是“可选的”,取决于元组中另一个元素的值,那么它仍然必须被构造,这对于某些类型来说仍然是非常浪费的。
如果您经常使用该成语,请考虑改用boost::optional
type 之类的东西。这可能比 go 的多次返回更接近 haskel。
http://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html
为此,在大多数情况下,我使用自己的包装器类型,它引入了一些语法糖。让我们看一个例子:
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;
};
然后,只需在可以返回错误的方法中使用此类作为返回值:
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
}
当然,阶乘函数的这种实现很糟糕,但演示了转换,而不用担心我们使用的错误扩展返回类型。
示例用法:
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;
}
}
输出:
-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
自定义类型的一大优点是您可以插入一些控件,以确保客户端代码始终在访问实际值之前检查错误,并且仅在成功时访问该值,如果不成功则访问错误消息。为此,我们可以通过以下方式扩展类:
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;
};
您可能希望根据构建模式(调试构建/发布构建)进行类定义。
请注意,示例必须重写如下:
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
}
}
上面的主代码仍然有效,因为它在访问值/错误消息之前已经进行了错误检查。
“你认为使用 std::pair 或 std:tuple 返回多个值的缺点是什么,即函数的返回值和错误/成功代码”
这种简单的(C 级)故障处理方法的主要缺点是
即还有更多可能出错的地方,例如访问不确定的结果值。或者只是在函数没有产生有意义的返回值时使用返回值。
旧的 Barton & NackmanFallow
类通过限制对结果值的访问解决了这个安全问题。本质上,调用代码必须在使用它之前检查是否存在结果 值,并且使用逻辑上不存在的结果值会导致异常或终止。这boost::optional
门课做的也差不多。
如果您不想依赖 Boost,那么Optional
对于 POD 结果类型实现一个类是微不足道的,并且以可能的低效率(动态分配)为代价,您可以只使用 astd::vector
来携带非 POD 可能的结果。
挑战在于保持调用代码的清晰性,这是练习的重点……
它比常规错误代码更好,因为您不必在没有参数的情况下浪费生命。但它仍然保留了所有非常严重的缺点。
真的,这只是一个微小的变化,它仍然是错误代码 - 没有显着的好处。