Guru of the Week 條款20:程式碼的複雜性(第一部分) (轉)

gugu99發表於2008-07-23
Guru of the Week 條款20:程式碼的複雜性(第一部分) (轉)[@more@]

GotW #20 Code Complexity – Part I:namespace prefix = o ns = "urn:schemas--com::office" />

著者:Herb Sutter

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

[宣告]:本文內容取自網站上的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操作可能丟擲異常。

 

*.  在將函式臨時的返回值複製到函式呼叫者的區域時,String的copy 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語句中的所有條件將總是被求值。

(完)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1007738/,如需轉載,請註明出處,否則將追究法律責任。

相關文章