可以/我应该使用std :: exception来进行常规错误处理吗?

时间:2015-04-28 21:28:14

标签: c++ error-handling idioms idiomatic

我将用C ++开始这个新项目,并且正在思考一种不痛苦的错误处理方法。现在,我不打算开始抛出和捕获异常,并且很可能永远不会抛出异常,但我在想 - 即使是常规错误处理,为什么要自己滚动/复制粘贴一个类来描述错误/状态,当我可以使用std::exception及其子类(或者可能是std::optional<std::exception>)时?

using Status = std::optional<std::exception>;
Status somethingThatMayFail(int x);

是否有人/任何项目以这种方式运作?这是一个荒谬的想法还是有点吱吱作响?

3 个答案:

答案 0 :(得分:3)

我不认为你应该构建例外情况,除非你真的打算扔它们。我会推荐一个bool或enum返回类型。阅读代码的人的意图会更清晰,而且速度会更快。但是,如果你构造一个异常,那么其他人会认为他们可以抛出异常并导致整个系统崩溃。

C ++异常在资源管理,触发析构函数和所有这些(RAII)中起着重要作用。以任何其他方式使用它们都会损害性能,并且(更重要的是)会让那些试图维护代码的人感到困惑。

但是,您可以使用与std :: exception无关的状态报告类来执行您想要的操作。人们为#34更快做了太多&#34;代码,当他们不需要时。如果状态枚举不够好,并且您需要返回更多信息,那么状态报告类将起作用。如果它使代码更容易阅读,那么去吧。

除非你实际扔掉它,否则不要把它称为例外。

答案 1 :(得分:2)

我认为单靠表现可能会有问题。请考虑以下代码:

#include <iostream>
#include <chrono>
#include <ctime>
#include <stdexcept>

#include <boost/optional.hpp>   


int return_code_foo(int i)   
{
    if(i < 10)  
        return -1;
    return 0;
} 


std::logic_error return_exception_foo(int i)   
{
    if(i < 10)  
        return std::logic_error("error");
    return std::logic_error("");
} 


boost::optional<std::logic_error> return_optional_foo(int i)   
{
    if(i < 10)  
        return boost::optional<std::logic_error>(std::logic_error("error"));
    return boost::optional<std::logic_error>();
} 


void exception_foo(int i)   
{
    if(i < 10)  
        throw std::logic_error("error");
} 


int main()
{
    std::chrono::time_point<std::chrono::system_clock> start, end;

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        return_code_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "code elapsed time: " << (end - start).count() << "s\n";

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        return_exception_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "exception elapsed time: " << (end - start).count() << "s\n";

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        return_optional_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "optional elapsed time: " << (end - start).count() << "s\n";

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        exception_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "exception elapsed time: " << (end - start).count() << "s\n";

    return 0;
}

在我的CentOS上,使用gcc 4.7,它的时间为:

[amit@amit tmp]$ ./a.out 
code elapsed time: 39893s
exception elapsed time: 466762s
optional elapsed time: 215282s
exception elapsed time: 38436s

在vanilla设置中,并且:

[amit@amit tmp]$ ./a.out 
code elapsed time: 0s
exception elapsed time: 238985s
optional elapsed time: 33595s
exception elapsed time: 24350

at -O2 settings。

P.S。我个人会使用异​​常/堆栈展开,因为它认为它是C +的基本部分,可能正如@vsoftco所说。

答案 2 :(得分:0)

要回答您的问题,这不是一个荒谬的想法,您实际上可以使用std::exception进行常规错误处理;有一些警告。

使用std::exception作为功能的结果

假设函数可以在几种错误状态下退出:

std::exception f( int i )
{
  if (i > 10)
    return std::out_of_range( "Index is out of range" );

  if ( can_do_f() )
    return unexpected_operation( "Can't do f in the current state" );

  return do_the_job();
}

如何使用std::exception或可选项处理此问题?当函数返回时,将创建异常的副本,仅保留std::exception部分并忽略实际错误的细节;留给你的唯一信息是“是的,出了什么问题......”。该行为与返回布尔值或预期结果类型的可选项(如果有)相同。

使用std::exception_ptr保存详细信息

另一种解决方案是应用与std::promise相同的方法,即返回std::exception_ptr。在那里,您将能够返回任何内容或异常,同时保留实际的错误详细信息。恢复错误的实际类型可能仍然很棘手。

在同一个对象中返回错误或结果

最后,另一种选择是使用Expected<T> proposalits implementation。在那里,您将能够在单个对象中返回值或错误,并根据您的意愿处理错误(通过测试错误或使用常规异常处理),对于函数返回无值的一些特殊情况(更多)在Stack Overflowthis blog上)。

如何选择

我个人对此事的看法是,如果您打算使用例外,那么按照设计的方式使用它们,最后使用Expected<T>之类的额外工具来简化它。否则,如果你不能使用标准的异常处理,那就去找一个已经证明自己的解决方案,比如经典的错误代码系统。