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

本文分析了一个只有四行代码的C++函数中可能存在的执行路径,探讨了短路求值规则、异常处理以及如何在简单代码中隐藏的复杂性。作者提出,在考虑异常情况时,这段代码可能有23种不同的执行路径,这影响了函数的可靠性和可测试性。
摘要由CSDN通过智能技术生成

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值