【问题标题】:C++ Error Handling - downside of using std::pair or std::tuple for returning error codes and function returns [closed]C++ 错误处理 - 使用 std::pair 或 std::tuple 返回错误代码和函数返回的缺点 [关闭]
【发布时间】:2012-11-26 12:15:42
【问题描述】:

在不讨论一般异常与错误代码的情况下,您认为使用std::pairstd:tuple 返回多个值的缺点是什么,即函数的返回值和错误/成功代码,类似于如何很多Go developers apparently do error handling

这种方法显然具有不必为函数返回值或错误代码使用参数的优点(取决于您喜欢哪种方式)。

【问题讨论】:

  • 查看旧的 Barton/Nackman Fallible 类,或更现代的版本,如 Boost optional
  • @close-voters:请仅投票关闭您理解的问题。不要仅仅因为您未能理解问题而投票结束。不理解并不意味着你有能力投票:它意味着相反
  • 有趣的事实——关于 CO 的最有用、最丰富和有教育意义的问题是封闭的,“没有建设性”、“太宽泛”等。由于线程现在已封闭,元组的另一个缺点——内存开销。 总是。除了异常,异常机制只有在出现异常时才会启动。

标签: c++ error-handling tuples return-value std-pair


【解决方案1】:

这个“成语”很好,因为类型和成功指示符都是函数的返回值。失败可能不是例外,所以例外有时是不合适的。

然而,缺点是您必须将两种返回类型分开。这可能很难看;使用std::tie 有帮助,但您无法从多个返回构造。

bool success;
std::string value;
std::tie(success, value)=try_my_func();

这很冗长。

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

如果您经常使用该成语,请考虑改用 boost::optional 类型。这可能比 go 的多次返回更接近 haskel。

参考

http://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html

【讨论】:

  • 同意处理退货很麻烦。另一种选择是auto result = try_my_func(); if (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__,__LINE__) )
  • @rici 你可以,我不确定我是否可以让自己使用宏,当需要一点冗长时,它仍然不能消除默认构造。
【解决方案2】:

为此,在大多数情况下,我使用自己的包装器类型,它引入了一些语法糖。我们来看一个例子:

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
    }
}

上面的主代码仍然有效,因为它在访问值/错误消息之前已经进行了错误检查。

【讨论】:

  • v 仍然需要构建(多重回报的主要缺点),使用类似 std::aligned_storage 的东西和放置更新会是更好的选择,或者只使用 Boost.Optional 已经可以了这个,并且有一条错误消息似乎将它的边界推向了异常领域。
  • 有时我不想使用 boost。有时(如果不是在 99% 的情况下),性能下降并不重要。不过,感谢您提及这一点。
【解决方案3】:

它比常规错误代码更好,因为您不必在没有参数的情况下浪费生命。但它仍然保留了所有非常严重的缺点。

真的,这只是一个微小的变化,它仍然是错误代码 - 没有显着的好处。

【讨论】:

  • 使用例如boost::optional 允许函数有时将“不产生结果”作为其正常调用合同的一部分,同时保持异常的安全性。对于“没有结果产生”的情况也使用正常返回对于调用代码的清晰性可能很重要。但这取决于...
  • 对于那些不想添加对 Boost 的依赖的人:optional 对于 POD 类型的实现是微不足道的,对于非 POD 可以简单地使用单个项目 std::vector 来实现转移可能的价值。在没有值的情况下,vector 只是空的(基本上这样做可以避免依赖并以可能的动态分配为代价获得简单的代码)。
【解决方案4】:

“您认为使用 std::pair 或 std:tuple 返回多个值的缺点是什么,即函数的返回值和错误/成功代码”

这种简单的(C 级)故障处理方法的主要缺点是

  • 失去安全。

即还有可能出错的地方,例如访问不确定的结果值。或者在函数没有产生有意义的返回值时使用返回值。

旧的 Barton & Nackman Fallow 类通过限制对结果值的访问来解决这个安全问题。本质上,调用代码必须在使用它之前检查是否存在 结果值,并且使用逻辑上不存在的结果值会导致异常或终止。 boost::optional 类的作用大致相同。

如果您不想依赖 Boost,那么对于 POD 结果类型实现 Optional 类是微不足道的,并且以可能的低效率(动态分配)为代价,您可以只使用 std::vector携带非 POD 可能的结果。

挑战在于保持调用代码的清晰性,这是练习的重点……

【讨论】:

  • @anonymous downvoter:请解释您的反对意见。投票不是表达不同意见的好方法,因为很少或没有人能读懂你的想法并理解是什么引发了你的反应。你有什么见解?或者你拥有哪些你选择不分享的知识?或者,或者,更有可能的是,你不明白什么?没有解释,读者是无法知道的。他们唯一知道的是你无法交流或分享。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-09
  • 1970-01-01
  • 2020-07-11
相关资源
最近更新 更多