Guru of the Week 条款20:代码的复杂性(第一部分)

    技术2022-05-11  248

    GotW #20 Code Complexity – Part I

    著者:Herb Sutter

    翻译:K ][ N G of @rk™

    [声明]:本文内容取自www.gotw.ca网站上的Guru of the Week栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。

    Revision 1.0

     

    Guru of the Week 条款20:代码的复杂性(第一部分)

     

    难度:9 / 10

     

    (本条款提出了一个有趣味的挑战:在一个简单得只有三行代码的函数里可以有多少条执行路经?其答案几乎将肯定让你吃惊。)

     

    [问题]

    在没有任何其它附加信息的情况下,下列代码中可以有多少条执行路经?

      String EvaluateSalaryAndReturnName( Employee e )   {     if( e.Title() == "CEO" || e.Salary() > 100000 )     {       cout << e.First() << " " << e.Last()            << " is overpaid" << endl;     }     return e.First() + " " + e.Last();   }

     

    [解答]

     

    假设:

    a)       忽略对函数参数求值时的不同顺序以及由析构函数(destructor)抛出的异常。[1]

     

      下面的问题提给无所畏惧的勇者:

      如果允许析构函数抛出异常,那么共会有多少条执行路经呢?

     

    b)      调用的函数被认为具有原子性。事实上,例如e.Title()这个调用就可能由于好几个原因而抛出异常(比如,它自己本身可能抛出异常;它也可能由于「未能捕获由其调用的另一个函数所抛出的异常」而抛出异常;或者它可能采用return by value(传值返回)方式从而造成临时对象得构造函数可能抛出异常)。这里我们假设对于函数而言,只关注执行e.Title()操作的结果,即完成该操作后是否抛出了异常。

     

    解答:23(仅仅在4行代码里!)

     

    如果你找到了                            给自己评等级

    ---------------------------------------------------------------------

    3                                               平均水平(Average

    4-14                                           能够认知异常(Exception-Aware

    15-23                                         精英资质(Guru Material

     

    23条执行路径包括:

      ——3条与异常无关的(non-exceptional)路径

      ——20条暗藏的路径,都与异常有关

     

    要理解那3条普通路径,诀窍就是要知道C/C++的“短路求值规则(Short-Circuit Evaluation Rule)”:

     

    1.            如果e.Title()==”CEO”,那么就不需要对第二个条件求值了(比如,e.Salary()将不会被调用),但cout还是会被执行的。[2]

    2.            如果e.Title()!=”CEO”e.Salary()>100000,那么两个条件都会被求值,cout会被执行。

    3.            如果e.Title()!=”CEO”e.Salary()<=100000,那么cout将不会被执行。

     

    下述都是由异常引出的执行路径:

     

      String EvaluateSalaryAndReturnName( Employee e )     ^*^                                       ^4^

    4.            引数采用pass by value(值传递)方式,这将唤起Employee copy constructor。这个copy操作可能抛出异常。

     

    *.         在将函数临时的返回值拷贝到函数调用者的区域时,Stringcopy constructor可能抛出异常。然而在这里我们忽略这种可能性,因为其是在函数外部发生的(何况从目前的情形来看,现有的执行路径已经够我们忙的了!)

     

        if( e.Title() == "CEO" || e.Salary() > 100000 )           ^5^     ^7^  ^6^ ^11^   ^8^    ^10^  ^9^

    5.            成员函数Title()本身就可能抛出异常;或者其采用return by value方式返回class type的对象,从而导致拷贝操作可能抛出异常。

    6.            为了与有效的operator==相匹配,字符串也许需要被转换成class type(或许与e.Title()的返回型别相同)的临时对象,而这个临时对象的构造过程可能抛出异常。

    7.            如果operator==是由程序员提供的函数,那么它可能抛出异常。

    8.            #5类似,Salary()本身可能抛出异常,或者由于其返回临时对象而造成在临时对象的构造过程中抛出异常。

    9.            #6类似,可能需要构造临时对象,而这个构造过程可能抛出异常。

    10.         #7类似,这或许是由程序员提供的函数,那么它可能抛出异常。

    11.         #7#10类似,这或许是由程序员提供的函数,那么它可能抛出异常。

     

          cout << e.First() << " " << e.Last()               ^17^                  ^18^            << " is overpaid" << endl;

    12-16      C++标准草案所述,这里的五个对operator<<的调用都可能抛出异常。

    17-18      #5类似。First()/Last()可能抛出异常,或者由于其返回临时对象而造成在对象的构造过程中可能抛出异常。

     

        return e.First()  +  " "   +   e.Last();                ^19^  ^22^^21^ ^23^  ^20^

    19-20  #5类似。First()/Last()可能抛出异常,或者由于其返回临时对象而造成在对象的构造过程中可能抛出异常。

    21.与#6类似,可能需要构造临时对象,而这个构造过程可能抛出异常。

    22-23  #7类似,这或许是由程序员提供的函数,那么它可能抛出异常。

     

    本期GotW条款的目的是演示「在一个允许异常机制的语言中,简单的代码里可以存在多少条暗藏的执行路径」。这种暗藏的复杂性会影响函数的可靠性和可测性吗?请在下一期GotW中寻找这个问题的答案。

     

    [1]:决不允许一个异常从析构函数中渗透出来。如果允许这样做,代码将无法正常工作。请看我在C++Report Nov/Dec 1997中有关的更多讨论:Destructors That Throw and Why They're Evil

     

    [2]:如果对==||>予以正确恰当的重载(overload),那么在if语句中,||或许是一个函数调用。如果其是一个函数调用,那么“短路求值规则”会被抑住,这样if语句中的所有条件将总是被求值。

    (完)


    最新回复(0)