c++ 异常 VS 错误码

引言

c++的错误处理这块 是用异常好?还是错误码好?争论不休,下面从code层面去谈谈什么时候用异常好,什么时候用错误码好。有句老话说得好,没有最好,只有更好。

错误码流派( error_code)

当然用错误码。异常会有“性能”损耗,确实会有微小性能问题,只不过是当异常发生时。异常会导致程序最后的编译结果变大。异常会层层传递 ,只要不捕获,会让我心爱的程序挂掉。。。。等等一系列理由。

下面举个实在的例子,来说明这种场景下的错误码“蛋疼”之处。无法恢复的错误,强行用错误码。

int test_10()
{
    if (open file)
    {
        read file;
        use data;
        return 0;
    }
    else
        return -1; //error,open failed
}

int test_9()
{
    auto ret = test_10();
    if(ret == -1)
        return -1;
    do_something();
    return 0;
}

int test_8()
{
    auto ret = test_9();
    if(ret == -1)
        return -1;
    do_something();
    return 0;
}

int test_7()
......
......
......
int test_1()

int test_0()
{
    auto ret = test_1();
    if(ret == -1)
        return -1;
    do_something();
    return 0;
}

int main()
{
    auto ret = test_0();
    if(ret == -1)
        return -1;
    do_something();
    return 0;
}

上面的函数调用层数高达10层。其中最核心的函数为test_10(),读一个文件的数据,然后拿来使用。其他函数都间接用到了test_10(),因为同属于一次“执行流”,如果test_10()出错,而其他函数的执行忽略这个出错的话,势必会导致整个“执行流”的逻辑出错,所以出现了上面每个函数都需要判断返回值,如果出错,就不能往下执行do_something()了。实际项目中的函数多如牛毛,各种调用眼花缭乱。所以出现了这样一类人,直接无视返回值,默认函数执行一定成功,因为这么层层+花式判断返回值确实让人蛋碎一地。

看到这是否想到了那些无视返回值的代码 ,导致Bug层出不穷的场景?这酸爽。。。。。

异常流派(exception)

当然用异常了 ,没看见上面的错误码判断都蛋碎一地了吗!!下面举个例子来说明 异常让你蛋碎一地的场景。可以恢复的错误,强行用异常。

class ConnectionException :public std::exception
{
    .....
};

void test()
{
    if (recv network date ok)
    {
        use data;
        return 0;
    }
    else
       throw ConnectionException();
}

void excute()
{
    try
    {
        test();
        do1();
        do2();
        ...
    }
    catch(ConnectionException & e)
    {
        拿到了test的错误了,满心欢喜地解决了。
        TM的怎么再回到do1()函数那 执行do1()啊?难道在这catch里 再写一次 do1();do2();... 这尼玛???不过总算解决了:
        do1();
        do2();
        ...
        那执行到do1(),do1()也抛异常了呢???再改!
        try
        {
            do1();
            do2();
            ...
        }
        catch()
        {
           拿到了do1()的错误了,满心欢喜地解决了。
           TM的怎么再回到do2()函数那 执行do2()啊?难道在这catch里 再写一次do2();... 这尼玛???一直这么嵌套下??这不炸了??
        }       
    }
}

所以解决方法只能这样:
void excute()
{
    try
    {
        test();
    }
    catch(ConnectionException & e)
    {
        拿到了test的错误了,满心欢喜地解决了。
    }

    try
    {
        do1();
    }
    catch()
    {
        拿到了do1()的错误了,满心欢喜地解决了。
    }

    try
    {
        do2();
    }
    catch()
    {
        拿到了do2()的错误了,满心欢喜地解决了。
    } 

    ...

    try{}
    catch(){}        
}




上面这种写个函数 就try一下,这多么似曾相识,和罗嗦的java差不多了。简直醉了,这要写多少个 try..catch ???所以出现了这样一类人,直接无视异常,默认函数执行一定成功,不会抛异常。因为这么层层+花式try..catch确实让人蛋碎一地。

看到这是否想到了那些无视异常的代码 ,导致程序中止层出不穷的场景?这酸爽。。。。。

何时用异常?何时用错误码?

上面都是强行乱用导致的代码书写混乱,让人头大无比。总结下如何用:

1、不可恢复的错误抛异常。只能抛异常后层层栈解退,这次的执行流无效,或者这个模块无效,亦或者整个程序无效。还能怎么办呢?

2、可恢复的错误,用返回值。如果发生了,随即就地解决。让这次的执行流,或者这个模块,或者整个程序欢快地执行下去。

针对上面的滥用,改为正确使用后,看看代码书写是否美观了

1)不可恢复的错误抛异常

class OpenFileException :public std::exception
{
    .....
};

void test_10()
{
    if(open file)
    {
        read data;
        use data;
        ...
    }
    else
        throw OpenFileException();
}

void test_9()
{
    test_10();
    do_something();
}

void test_8()
{
    test_9();
    do_something();
}

void test_7()
......
......
......
void test_1()

void test_0()
{
    test_1();
    do_something();
}

int main()
{
    try
    {
        test_0();
        do_something();
    }
    catch(std::exception& e)
    {
        log;
        return -1;
    }
    return 0;
}

最后在main处catch 一次,记录下日志,也只能记录日志,这种错误你无法解决。所有间接用到了test_10()的函数无需管他的返回值,(就是管了,你能咋的??)简洁完美。

2)可恢复的错误


int test()
{
    if (recv network date ok)
    {
        use data;
        return 0;
    }
    else
       return -1; // disconnect
}

void excute()
{
    auto ret = test();
    if(ret==-1)
    {
        满心欢喜地解决
    }
    ret = do1();
    if(ret==-1)
    {
        满心欢喜地解决
    }
    ret = do2();
    if(ret==-1)
    {
        满心欢喜地解决
    } 
    ... 
}

总结

引用来自isocpp的最后一段话:(https://isocpp.org/wiki/faq/exceptions

Why can’t I resume after catching an exception?

In other words, why doesn’t C++ provide a primitive for returning to the point from which an exception was thrown and continuing execution from there?

Basically, someone resuming from an exception handler can never be sure that the code after the point of throw was written to deal with the execution just continuing as if nothing had happened. An exception handler cannot know how much context to “get right” before resuming. To get such code right, the writer of the throw and the writer of the catch need intimate knowledge of each others code and context. This creates a complicated mutual dependency that wherever it has been allowed has led to serious maintenance problems.

Stroustrup seriously considered the possibility of allowing resumption when he designed the C++ exception handling mechanism and this issue was discussed in quite some detail during standardization. See the exception handling chapter of The Design and Evolution of C++.

If you want to check to see if you can fix a problem before throwing an exception, call a function that checks and then throws only if the problem cannot be dealt with locally. A new_handler is an example of this.

猜你喜欢

转载自blog.csdn.net/qq_15328161/article/details/88758268
今日推荐