C++ C++;错误处理-使用std::pair或std::tuple返回错误代码和函数返回的缺点
在不讨论一般异常与错误代码的情况下,您认为使用C++ C++;错误处理-使用std::pair或std::tuple返回错误代码和函数返回的缺点,c++,error-handling,tuples,return-value,std-pair,C++,Error Handling,Tuples,Return Value,Std Pair,在不讨论一般异常与错误代码的情况下,您认为使用std::pair或std:tuple返回多个值的缺点是什么,即函数的返回值和错误/成功代码,类似于多少 这种方法的优点显然是不必为函数返回值或错误代码使用参数(取决于您喜欢的方式)。这种“习惯用法”很好,因为type和success指示符都是函数的返回值。失败可能并不例外,所以有时候例外是不合适的 然而,缺点是你必须将这两种回报类型分开。这可能是丑陋的;使用std::tie会有所帮助,但您无法从多个返回中构造 bool success; std::
std::pair
或std:tuple
返回多个值的缺点是什么,即函数的返回值和错误/成功代码,类似于多少
这种方法的优点显然是不必为函数返回值或错误代码使用参数(取决于您喜欢的方式)。这种“习惯用法”很好,因为type和success指示符都是函数的返回值。失败可能并不例外,所以有时候例外是不合适的
然而,缺点是你必须将这两种回报类型分开。这可能是丑陋的;使用std::tie
会有所帮助,但您无法从多个返回中构造
bool success;
std::string value;
std::tie(success, value)=try_my_func();
这太冗长了
第二,如果其中一个类型是“可选的”,这取决于元组中另一个元素的值,那么它仍然必须被构造,这对于某些类型来说仍然是非常浪费的
<>如果你使用了大量的成语,可以考虑使用诸如bool success;
std::string value;
std::tie(success, value)=try_my_func();
参考文献
为此,在大多数情况下,我使用自己的包装器类型,它引入了一些语法上的糖分。让我们看一个例子:
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;
};
自定义类型的一大优点是,您可以插入一些控件,确保客户端代码在访问实际值之前始终检查错误,并且仅在成功时访问该值,如果未成功,则访问错误消息。为此,我们可以通过以下方式扩展该类:
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
}
}
结果fac(int n){
if(n<0)
返回结果::错误(“n必须大于或等于零!”);
如果(n==0)
返回1;
如果(n>0){
结果r=fac(n-1);
if(r.isError())返回r;//传播错误(类似于异常)
return n*r;//r自动转换为int
}
}
上面的主代码仍然有效,因为它在访问值/错误消息之前已经进行了错误检查。它比常规错误代码好,因为您不必在没有参数的情况下浪费生命。但它仍然保留着所有非常严重的缺点 事实上,这只是一个微小的变化,仍然是错误代码——没有明显的好处 “您认为使用std::pair或std:tuple返回多个值(即函数的返回值和错误/成功代码)的缺点是什么?” 这种简单化(C级)的故障处理方法的主要缺点是
- 失去安全感
Fallow
类通过限制对结果值的访问来解决这个安全问题。本质上,调用代码在使用它之前必须检查是否存在结果值,并且使用逻辑上不存在的结果值会导致异常或终止。boost::optional
类的作用大致相同
如果您不想依赖Boost,那么对于POD结果类型来说,可选的
类很难实现,并且以一点可能的低效率(动态分配)为代价,您可以使用一个std::vector
来携带一个非POD可能的结果
挑战在于保持调用代码的清晰性,这是整个练习的重点…一致认为处理返回很难。另一种方法是
auto result=try_my_func();如果(result.first){std::string&value=result.second;…}
,在某些情况下这有点自然。@SteveJessop是的,使用auto有点帮助,但我发现获取参考资料非常“嘈杂”,希望有一天能够从一个元组中构造,这会被添加到语言中。只需编写result.second
几次:这很烦人,但做任何事情都不够烦人:-)@111111,我想你可以用一个宏,比如#define DECL_TIE(X,Y,EXPR)auto result=EXPR;auto&X=result.first;auto&Y=result.second
当然除了你想对result
使用某种gensym之外(CONCAT(例如result\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,它仍然没有消除默认构造。v
仍然必须构造(多返回的一个主要缺点),使用类似于std::aligned_storage
和placement newing的东西将是更好的选择,或者只使用Boost.Optional
,它已经做到了这一点,有一条错误消息似乎把它的边界推到了异常区域。有时我不想使用boost。有时(如果不是在99%的情况下),性能下降并不重要。不过,感谢您提到这一点。请查看旧的Barton/NackmanFallible
类,或更现代的版本,如Boostoptional
@close投票者:请只对您理解的问题进行投票。不要仅仅因为你不理解问题就投票结束。不理解并不意味着你有能力投票:它意味着相反的有趣事实——最有用、最丰富、最有教育意义的问题
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
}
}