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();
这太冗长了

第二,如果其中一个类型是“可选的”,这取决于元组中另一个元素的值,那么它仍然必须被构造,这对于某些类型来说仍然是非常浪费的

<>如果你使用了大量的成语,可以考虑使用诸如Boo::可选的类型。这是接近哈斯克尔也许比去的多次回报

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级)的故障处理方法的主要缺点是

  • 失去安全感
也就是说,还有更多可能出错,例如访问不确定的结果值。或者在函数没有生成有意义的返回值时使用返回值

旧的Barton&Nackman
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/Nackman
Fallible
类,或更现代的版本,如Boost
optional
@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
    }
}