偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

為什么我希望用C而不是C++來(lái)實(shí)現(xiàn)ZeroMQ

開(kāi)發(fā) 后端 項(xiàng)目管理
開(kāi)始前我要先做個(gè)澄清:這篇文章同Linus Torvalds這種死忠C程序員吐槽C++的觀點(diǎn)是不同的。在我的整個(gè)職業(yè)生涯里我都在使用C++,而且現(xiàn)在C++依然是我做大多數(shù)項(xiàng)目時(shí)的首選編程語(yǔ)言。

開(kāi)始前我要先做個(gè)澄清:這篇文章同Linus Torvalds這種死忠C程序員吐槽C++的觀點(diǎn)是不同的。在我的整個(gè)職業(yè)生涯里我都在使用C++,而且現(xiàn)在C++依然是我做大多數(shù)項(xiàng)目時(shí)的首選編程語(yǔ)言。自然的,當(dāng)我從2007年開(kāi)始做ZeroMQ(ZeroMQ項(xiàng)目主頁(yè))時(shí),我選擇用C++來(lái)實(shí)現(xiàn)。主要的原因有以下幾點(diǎn):

1.  包含數(shù)據(jù)結(jié)構(gòu)和算法的庫(kù)(STL)已經(jīng)成為這個(gè)語(yǔ)言的一部分了。如果用C,我將要么依賴第三方庫(kù)要么不得不自己手動(dòng)寫(xiě)一些自1970年來(lái)就早已存在的基礎(chǔ)算法。

2.  C++語(yǔ)言本身在編碼風(fēng)格的一致性上起到了一些強(qiáng)制作用。比如,有了隱式的this指針參數(shù),這就不允許通過(guò)各種不同的方式將指向?qū)ο蟮闹羔樧鲛D(zhuǎn)換,而那種做法在C項(xiàng)目中常常見(jiàn)到(通過(guò)各種類(lèi)型轉(zhuǎn)換)。同樣的還有可以顯式的將成員變量定義為私有的,以及許多其他的語(yǔ)言特性。

3.  這個(gè)觀點(diǎn)基本上是前一個(gè)的子集,但值得我在這里顯式的指出:用C語(yǔ)言實(shí)現(xiàn)虛函數(shù)機(jī)制比較復(fù)雜,而且對(duì)于每個(gè)類(lèi)來(lái)說(shuō)會(huì)有些許的不同,這使得對(duì)代碼的理解和維護(hù)都會(huì)成為痛苦之源。

4.  最后一點(diǎn)是:人人都喜歡析構(gòu)函數(shù),它能在變量離開(kāi)其作用域時(shí)自動(dòng)得到調(diào)用。

如今,5年過(guò)去了,我想公開(kāi)承認(rèn):用C++作為ZeroMQ的開(kāi)發(fā)語(yǔ)言是一個(gè)糟糕的選擇,后面我將一一解釋為什么我會(huì)這么認(rèn)為。

首先,很重要的一點(diǎn)是ZeroMQ是需要長(zhǎng)期連續(xù)不停運(yùn)行的一個(gè)網(wǎng)絡(luò)庫(kù)。它應(yīng)該永遠(yuǎn)不會(huì)出錯(cuò),而且永遠(yuǎn)不能出現(xiàn)未定義的行為。因此,錯(cuò)誤處理對(duì)于ZeroMQ來(lái)說(shuō)至關(guān)重要,錯(cuò)誤處理必須是非常明確的而且對(duì)錯(cuò)誤應(yīng)該是零容忍的。

C++的異常處理機(jī)制卻無(wú)法滿足這個(gè)要求。C++的異常機(jī)制對(duì)于確保程序不會(huì)失敗是非常有效的——只要將主函數(shù)包裝在try/catch塊中,然后你就可以在一個(gè)單獨(dú)的位置處理所有的錯(cuò)誤。然而,當(dāng)你的目標(biāo)是確保沒(méi)有未定義行為發(fā)生時(shí),噩夢(mèng)就產(chǎn)生了。C++中引發(fā)異常和處理異常是松耦合的,這使得在 C++中避免錯(cuò)誤是十分容易的,但卻使得保證程序永遠(yuǎn)不會(huì)出現(xiàn)未定義行為變得基本不可能。

在C語(yǔ)言中,引發(fā)錯(cuò)誤和處理錯(cuò)誤的部分是緊耦合的,它們?cè)谠创a中處于同一個(gè)位置。這使得我們?cè)阱e(cuò)誤發(fā)生時(shí)能很容易理解到底發(fā)生了什么:

  1. int rc = fx ();  
  2. if (rc != 0)  
  3.     handle_error(); 

在C++中,你只是拋出一個(gè)異常,到底發(fā)生了什么并不能馬上得知。

  1. int rc = fx();  
  2. if (rc != 0)  
  3.     throw std::exception(); 

這里的問(wèn)題就在于你對(duì)于誰(shuí)處理這個(gè)異常,以及在哪里處理這個(gè)異常是不得而知的。如果你把異常處理代碼也放在同一個(gè)函數(shù)中,這么做或多或少還有些明智,盡管這么做會(huì)犧牲一點(diǎn)可讀性。

  1. try {  
  2.     …  
  3.     int rc = fx();  
  4.     if (rc != 0)  
  5.     throw std::exception(“Error!”);  
  6.     …  
  7. catch (std::exception &e) {  
  8.     handle_exception();  

但是,考慮一下,如果同一個(gè)函數(shù)中拋出了兩個(gè)異常時(shí)會(huì)發(fā)生什么?

  1. class exception1 {};  
  2. class exception2 {};  
  3. try {  
  4.     …  
  5.     if (condition1)  
  6.         throw my_exception1();  
  7.     …  
  8.     if (condition2)  
  9.         throw my_exception2();  
  10.     …  
  11. }  
  12. catch (my_exception1 &e) {  
  13.     handle_exception1();  
  14. }  
  15. catch (my_exception2 &e) {  
  16.     handle_exception2();  

對(duì)比一下相同的C代碼:

  1. …  
  2. if (condition1)  
  3.     handle_exception1();  
  4. …  
  5. if (condition2)  
  6.     handle_exception2();  
  7. … 

C代碼的可讀性明顯高的多,而且還有一個(gè)附加的優(yōu)勢(shì)——編譯器會(huì)為此產(chǎn)生更高效的代碼。這還沒(méi)完呢。再考慮一下這種情況:異常并不是由所拋出異常的函數(shù)來(lái)處理。在這種情況下,異常處理可能發(fā)生在任何地方,這取決于這個(gè)函數(shù)是在哪調(diào)用的。雖然乍一看我們可以在不同的上下文中處理不同的異常,這似乎很有用,但很快就會(huì)變成一場(chǎng)噩夢(mèng)。

當(dāng)你在解決bug的時(shí)候,你會(huì)發(fā)現(xiàn)幾乎同樣的錯(cuò)誤處理代碼在許多地方都出現(xiàn)過(guò)。在代碼中增加一個(gè)新的函數(shù)調(diào)用可能會(huì)引入新的麻煩,不同類(lèi)型的異常都會(huì)涌到調(diào)用函數(shù)這里,而調(diào)用函數(shù)本身并沒(méi)有適當(dāng)進(jìn)行的處理,這意味著什么?新的bug。

如果你依然堅(jiān)持要杜絕“未定義的行為”,你不得不引入新的異常類(lèi)型來(lái)區(qū)分不同的錯(cuò)誤模式。然而,增加一個(gè)新的異常類(lèi)型意味著它會(huì)涌現(xiàn)在各個(gè)不同的地方,那么就需要在所有這些地方都增加一些處理代碼,否則你又會(huì)出現(xiàn)“未定義的行為”。到這里你可能會(huì)尖叫:這特么算什么異常規(guī)范哪!

好吧,問(wèn)題就在于異常規(guī)范只是以一種更加系統(tǒng)化的方式,以按照指數(shù)規(guī)模增長(zhǎng)的異常處理代碼來(lái)處理問(wèn)題的工具,它并沒(méi)有解決問(wèn)題本身。甚至可以說(shuō)現(xiàn)在情況更加糟糕了,因?yàn)槟悴坏貌蝗?xiě)新的異常類(lèi)型,新的異常處理代碼,以及新的異常規(guī)范。

通過(guò)上面我描述的問(wèn)題,我決定使用去掉異常處理機(jī)制的C++。這正是ZeroMQ以及Crossroads I/O今天的樣子。但是,很不幸,問(wèn)題到這并沒(méi)有結(jié)束…

考慮一下當(dāng)一個(gè)對(duì)象初始化失敗的情況。構(gòu)造函數(shù)沒(méi)有返回值,因此出錯(cuò)時(shí)只能通過(guò)拋出異常來(lái)通知出現(xiàn)了錯(cuò)誤??墒俏乙呀?jīng)決定不使用異常了,那么我不得不這樣做:

  1. class foo  
  2. {  
  3. public:  
  4.     foo();  
  5.     int init();  
  6.     …  
  7. }; 

當(dāng)你創(chuàng)建這個(gè)類(lèi)的實(shí)例時(shí),構(gòu)造函數(shù)被調(diào)用(不允許失?。?,然后你顯式的去調(diào)用init來(lái)初始化(init可能會(huì)失敗)對(duì)象。相比于C語(yǔ)言中的做法,這就顯得過(guò)于復(fù)雜了。

  1. struct foo  
  2. {  
  3.     …  
  4. };  
  5. int foo_init(struct foo *self); 

但是以上的例子中,C++版本真正邪惡的地方在于:如果有程序員往構(gòu)造函數(shù)中加入了一些真正的代碼,而不是將構(gòu)造函數(shù)留空時(shí)會(huì)發(fā)生什么?如果有人真的這么做了,那么就會(huì)出現(xiàn)一個(gè)新的特殊的對(duì)象狀態(tài)——“半初始化狀態(tài)”。這種狀態(tài)是指對(duì)象已經(jīng)完成了構(gòu)造(構(gòu)造函數(shù)調(diào)用完成,且沒(méi)有失?。玦nit函數(shù)還沒(méi)有被調(diào)用。我們的對(duì)象需要修改(特別是析構(gòu)函數(shù)),這里應(yīng)該以一種方式妥善的處理這種新的狀態(tài),這就意味著又要為每一個(gè)方法增加新的條件。

看到這里你可能會(huì)說(shuō):這就是你人為的限制使用異常處理所帶來(lái)的后果啊!如果在構(gòu)造函數(shù)中拋出異常,C++運(yùn)行時(shí)庫(kù)會(huì)負(fù)責(zé)清理適當(dāng)?shù)膶?duì)象,那這里根本就沒(méi)有什么“半初始化狀態(tài)”了!很好,你說(shuō)的很對(duì),但這根本無(wú)關(guān)緊要。如果你使用異常,你就不得不處理所有那些與異常相關(guān)的復(fù)雜情況(我前面已經(jīng)描述過(guò)了)。而這對(duì)于一個(gè)面對(duì)錯(cuò)誤時(shí)需要非常健壯的基礎(chǔ)組件來(lái)說(shuō)并不是一個(gè)合理的選擇。

此外,就算初始化不是問(wèn)題,那析構(gòu)的時(shí)候絕對(duì)會(huì)有問(wèn)題。你不能在析構(gòu)函數(shù)中拋出異常,這可不是什么人為的限制,而是如果析構(gòu)函數(shù)在堆棧輾轉(zhuǎn)開(kāi)解(stack unwinding)的過(guò)程中剛好拋出一個(gè)異常的話,那整個(gè)進(jìn)程都會(huì)因此而崩潰。因此,如果析構(gòu)過(guò)程可能失敗的話,你需要兩個(gè)單獨(dú)的函數(shù)來(lái)搞定它:

  1. class foo  
  2. {  
  3. public:  
  4.     …  
  5.     int term();  
  6.     ~foo();  
  7. }; 

現(xiàn)在,我們又回到了前面初始化的問(wèn)題上來(lái)了:這里出現(xiàn)了一個(gè)新的“半終止?fàn)顟B(tài)”需要我們?nèi)ヌ幚?,又需要為成員函數(shù)增加新的條件了…

  1. class foo  
  2. {  
  3. public:  
  4.     foo () : state (semi_initialised)  
  5.     {  
  6.          ...  
  7.     }  
  8.    
  9.     int init ()  
  10.     {  
  11.         if (state != semi_initialised)  
  12.             handle_state_error ();  
  13.         ...  
  14.         state = intitialised;  
  15.     }  
  16.    
  17.     int term ()  
  18.     {  
  19.          if (state != initialised)  
  20.              handle_state_error ();  
  21.          ...  
  22.          state = semi_terminated;  
  23.     }  
  24.    
  25.     ~foo ()  
  26.     {  
  27.          if (state != semi_terminated)  
  28.              handle_state_error ();  
  29.          ...  
  30.     }  
  31.    
  32.     int bar ()  
  33.     {  
  34.          if (state != initialised)  
  35.              handle_state_error ();  
  36.          ...  
  37.     }  
  38. }; 

將上面的例子與同樣的C語(yǔ)言實(shí)現(xiàn)做下對(duì)比。C語(yǔ)言版本中只有兩個(gè)狀態(tài)。未初始化狀態(tài):整個(gè)結(jié)構(gòu)體可以包含隨機(jī)的數(shù)據(jù);以及初始化狀態(tài):此時(shí)對(duì)象完全正常,可以投入使用。因此,根本沒(méi)必要在對(duì)象中加入一個(gè)狀態(tài)機(jī)。

  1. struct foo  
  2. {  
  3.     ...  
  4. };  
  5.    
  6. int foo_init ()  
  7. {  
  8.     ...  
  9. }  
  10.    
  11. int foo_term ()  
  12. {  
  13.     ...  
  14. }  
  15.    
  16. int foo_bar ()  
  17. {  
  18.     ...  

現(xiàn)在,考慮一下當(dāng)你把繼承機(jī)制再加到這趟渾水中時(shí)會(huì)發(fā)生什么。C++允許把對(duì)基類(lèi)的初始化作為派生類(lèi)構(gòu)造函數(shù)的一部分。拋出異常時(shí)將析構(gòu)掉對(duì)象已經(jīng)成功初始化的那部分。

  1. class foo: public bar  
  2. {  
  3. public:  
  4.     foo ():bar () {}  
  5.     …  
  6. }; 

但是,一旦你引入單獨(dú)的init函數(shù),那么對(duì)象的狀態(tài)數(shù)量就會(huì)增加。除了“未初始化”、“半初始化”、“初始化”、“半終止”狀態(tài)外,你還會(huì)遇到這些狀態(tài)的各種組合??!打個(gè)比方,你可以想象一下一個(gè)完全初始化的基類(lèi)和一個(gè)半初始化狀態(tài)的派生類(lèi)。

這種對(duì)象根本不可能保證有確定的行為,因?yàn)橛刑酄顟B(tài)的組合了。鑒于導(dǎo)致這類(lèi)失敗的原因往往非常罕見(jiàn),于是大部分相關(guān)的代碼很可能未經(jīng)過(guò)測(cè)試就進(jìn)入了產(chǎn)品。

總結(jié)以上,我相信這種“定義完全的行為”(fully-defined behaviour)打破了面向?qū)ο缶幊痰哪P?。這不是專門(mén)針對(duì)C++的,而是適用于任何一種帶有構(gòu)造函數(shù)和析構(gòu)函數(shù)機(jī)制的面向?qū)ο缶幊陶Z(yǔ)言。

因此,似乎面向?qū)ο缶幊陶Z(yǔ)言更適合于當(dāng)快速開(kāi)發(fā)的需求比杜絕一切未定義行為要更為重要的場(chǎng)景中。這里并沒(méi)有銀彈,系統(tǒng)級(jí)編程將不得不依賴于C語(yǔ)言。

最后順帶提一下,我已經(jīng)開(kāi)始將Crossroads I/O(ZeroMQ的fork,我目前正在做的)由C++改寫(xiě)為C版本。代碼看起來(lái)棒極了!

 

譯注:這篇新出爐的文章引發(fā)了大量的回復(fù),有覺(jué)得作者說(shuō)的很對(duì)的,也有人認(rèn)為這根本不是C++的問(wèn)題,而是作者錯(cuò)誤的使用了異常,以及設(shè)計(jì)上的失誤,也有讀者提到了Go語(yǔ)言可能是種更好的選擇。好在作者也都能積極的響應(yīng)回復(fù),于是產(chǎn)生了不少精彩的技術(shù)討論。建議中國(guó)的程序員們也可以看看國(guó)外的開(kāi)發(fā)者們對(duì)于這種“吐槽”類(lèi)文章的態(tài)度以及他們討論問(wèn)題的方式。

 

英文原文:martin_sustrik

原文鏈接:http://blog.jobbole.com/19647/

【編輯推薦】

  1. 郗曉勇:如何減小與“大牛”的差距
  2. 程序員漫畫(huà)四幅:要錢(qián)還是要命?
  3. 一個(gè)人是否能成為程序員是上天注定的
責(zé)任編輯:林師授 來(lái)源: 伯樂(lè)在線
相關(guān)推薦

2020-09-15 09:23:19

C++WindowsC#

2019-04-19 11:56:48

框架AI開(kāi)發(fā)

2012-10-10 16:52:21

CentOSDebianUbuntu

2013-06-24 15:32:00

c++GCC

2017-09-11 19:58:06

PostgreSQLMySQL數(shù)據(jù)庫(kù)

2023-10-30 10:29:50

C++最小二乘法

2022-11-28 09:58:58

C++開(kāi)發(fā)

2021-03-26 11:50:28

Linuxexals

2021-12-03 17:22:09

CC++編程語(yǔ)言

2023-11-02 08:20:54

SocketZygoteAndroid

2016-01-12 16:58:31

C游戲

2010-01-20 14:03:12

C++程序

2010-01-22 15:14:37

學(xué)習(xí)C++

2024-01-24 11:24:03

C++編程異常處理

2020-09-22 15:29:03

UnixC++C

2013-03-25 10:14:18

NginxApache

2021-11-19 09:49:00

CC++語(yǔ)法糖

2014-04-24 13:43:37

CC++單元測(cè)試框架

2021-08-14 09:04:58

TypeScriptJavaScript開(kāi)發(fā)

2021-10-30 19:57:00

HTTP2 HTTP
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)