1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        我為什么放棄 C++,選擇 C 語(yǔ)言編寫(xiě)個(gè)人項(xiàng)目?

        共 3955字,需瀏覽 8分鐘

         ·

        2022-11-22 02:33

        本文經(jīng)授權(quán)轉(zhuǎn)自公眾號(hào)CSDN(ID:CSDNnews)

        作者 | Martin Sústrik
        譯者 | 彎月,責(zé)編 | 屠敏

        【編者按】一直以來(lái),C 和 C++ 都是非常優(yōu)秀的編程語(yǔ)言。不過(guò),兩種語(yǔ)言雖名稱有些相似,但應(yīng)用場(chǎng)景存在巨大的不同。對(duì)于 C 語(yǔ)言而言,其主要被用于操作系統(tǒng)、容器、物聯(lián)網(wǎng)、數(shù)據(jù)庫(kù)等領(lǐng)域的開(kāi)發(fā),而 C++ 則是開(kāi)發(fā)桌面軟件、圖形處理、游戲、網(wǎng)站的最佳工具。在本文中,作者原以為 C++ 在開(kāi)發(fā)基礎(chǔ)設(shè)施時(shí)會(huì)更勝一籌,然而經(jīng)過(guò)與 C 語(yǔ)言的嘗試對(duì)比,發(fā)現(xiàn)事實(shí)并非如此。


        以下為翻譯正文:

        首先聲明,在整個(gè)職業(yè)生涯中,我一直在使用C++,而且在做大多數(shù)項(xiàng)目時(shí),C++仍然是我的首選語(yǔ)言。

        因此,在開(kāi)始構(gòu)建個(gè)人項(xiàng)目ZeroMQ(可伸縮的分布式或并發(fā)應(yīng)用程序設(shè)計(jì)的高性能異步消息庫(kù))時(shí),我也選用了C++,主要原因如下:

        1. C++包含一些數(shù)據(jù)結(jié)構(gòu)和算法庫(kù)。如果使用C語(yǔ)言,我將不得不依賴第三方庫(kù),或者自己動(dòng)手編寫(xiě)基本算法。

        2. C++會(huì)強(qiáng)制我在編程風(fēng)格上保持一些基本的統(tǒng)一性。例如,this參數(shù)不允許使用幾種不同的機(jī)制將指針傳遞給正在處理的對(duì)象,而這個(gè)問(wèn)題在C項(xiàng)目中很常見(jiàn)。同樣,不可以明確將成員變量標(biāo)記為私有,此外還有C++的一些其他特征。

        3. 使用C語(yǔ)言實(shí)現(xiàn)虛函數(shù)非常復(fù)雜,會(huì)導(dǎo)致理解和管理代碼的難度加劇。不過(guò),嚴(yán)格來(lái)說(shuō),這個(gè)問(wèn)題其實(shí)是上一個(gè)問(wèn)題的一個(gè)子集,但我覺(jué)得有必要單獨(dú)指出。

        4. 最后,每個(gè)人都喜歡在代碼的末尾自動(dòng)調(diào)用析構(gòu)函數(shù)。

        然而,事到如今,我不得不承認(rèn)C++是一個(gè)糟糕的選擇。下面,我來(lái)解釋一下原因。

        首先,我的個(gè)人項(xiàng)目ZeroMQ是一個(gè)持續(xù)運(yùn)行的基礎(chǔ)設(shè)施,永遠(yuǎn)不應(yīng)該出故障,永遠(yuǎn)不應(yīng)該表現(xiàn)出未定義的行為。因此,錯(cuò)誤處理至關(guān)重要,必須做到明確且嚴(yán)格。

        然而,C++的異常處理并不能滿足我的需求。如果程序不會(huì)出錯(cuò),那么選擇C++沒(méi)有任何問(wèn)題,只需將main函數(shù)包裝在try/catch中,集中在一個(gè)地方處理所有錯(cuò)誤。

        如果你的目標(biāo)是保證不會(huì)出現(xiàn)未定義的行為,那么C++的異常處理就會(huì)變成一場(chǎng)噩夢(mèng)。由于C++解耦了異常的發(fā)生與處理,因此錯(cuò)誤處理非常容易,但也造成了你幾乎不可能保證程序永遠(yuǎn)不會(huì)運(yùn)行未定義的行為。

        在C語(yǔ)言中,錯(cuò)誤的產(chǎn)生和處理是緊密結(jié)合的,在同一塊源代碼中。因此,在出錯(cuò)時(shí)很容易理解發(fā)生了什么:

        int rc = fx ();if (rc != 0)handle_error ();

        而在C++中,你只能拋出錯(cuò)誤,卻不清楚究竟發(fā)生了什么:

        int rc = fx ();if (rc != 0)throw std::exception ();

        問(wèn)題在于,你并不清楚在哪里處理異常。處理錯(cuò)誤的代碼在同一個(gè)函數(shù)中會(huì)更加方便理解,盡管不太方便閱讀:

        try {...int rc = fx ();if (rc != 0)throw std::exception ("Error!");...catch (std::exception &e) {handle_exception ();}

        然而,我們來(lái)考慮同一個(gè)函數(shù)拋出兩個(gè)不同的錯(cuò)誤,結(jié)果會(huì)怎么樣:

        class exception1 {};class exception2 {};try {...if (condition1)throw my_exception1 ();...if (condition2)throw my_exception2 ();...}catch (my_exception1 &e) {handle_exception1 ();}catch (my_exception2 &e) {handle_exception2 ();}

        以下是等效的C代碼:

        ...if (condition1)handle_exception1 ();...if (condition2)handle_exception2 ();...

        相較之下,C語(yǔ)言更加方便閱讀,而且編譯器也會(huì)生成更高效的代碼。

        然而,C++的問(wèn)題還不僅限于此。考慮某個(gè)函數(shù)會(huì)引發(fā)異常,但不會(huì)處理異常的情況。在這種情況下,錯(cuò)誤的處理可以放到任何地方,具體取決于從哪里調(diào)用該函數(shù)。

        針對(duì)不同的情況,采用不同的方式處理異常?這種方法聽(tīng)起來(lái)似乎很有道理,但很快就會(huì)變成一場(chǎng)噩夢(mèng)。

        在修復(fù)某個(gè)Bug時(shí),你會(huì)發(fā)現(xiàn)許多其他地方也有相同的Bug,因?yàn)樗鼈兌紡?fù)制了同一段錯(cuò)誤處理代碼。每當(dāng)添加一個(gè)函數(shù)調(diào)用,就有可能增加一個(gè)新異常,如果調(diào)用函數(shù)的代碼沒(méi)有妥善處理該異常,就意味著增加了一個(gè)新Bug。

        如果你還想堅(jiān)持“沒(méi)有未定義的行為”原則,就不得不引入新異常,以便區(qū)分不同的故障模式。但是,添加新異常就意味著,它會(huì)上升到不同的地方。你必須在所有地方添加相應(yīng)的異常處理,否則就會(huì)出現(xiàn)未定義的行為。

        看到這里,你可能想說(shuō):這就是異常的正確用法???

        然而問(wèn)題在于,異常只是一個(gè)工具,目的是用更系統(tǒng)的方式管理呈現(xiàn)指數(shù)增長(zhǎng)的錯(cuò)誤處理代碼,但它并不能解決根本的問(wèn)題。甚至可以說(shuō),異常有可能導(dǎo)致情況惡化,因?yàn)槟悴粌H需要編寫(xiě)新的異常類(lèi)型,還需要針對(duì)新類(lèi)型編寫(xiě)異常處理代碼。

        考慮到上述問(wèn)題,我決定使用C++,但不使用異常。如今我的這個(gè)項(xiàng)目就是這樣實(shí)現(xiàn)的。

        不幸的是,問(wèn)題并沒(méi)有就此止步……

        考慮一下,如果對(duì)象的初始化失敗,會(huì)發(fā)生什么?構(gòu)造函數(shù)沒(méi)有返回值,因此只能通過(guò)拋出異常來(lái)報(bào)告失敗。但是,我決定不使用異常。所以,我們必須像下面這樣處理:

        class foo{public:foo ();int init ();...};

        在創(chuàng)建實(shí)例時(shí),會(huì)調(diào)用構(gòu)造函數(shù)(這個(gè)函數(shù)不會(huì)失?。?,然后調(diào)用init函數(shù)(這個(gè)函數(shù)可能會(huì)失?。?。

        與C語(yǔ)言相比,C++代碼更復(fù)雜:

        struct foo{...};int foo_init (struct foo *self);

        然而,C++代碼真正的問(wèn)題在于,如果開(kāi)發(fā)人員在構(gòu)造函數(shù)中編寫(xiě)一些代碼,會(huì)發(fā)生什么?

        在這種情況下,會(huì)出現(xiàn)一個(gè)特殊的新對(duì)象狀態(tài)。由于對(duì)象已構(gòu)造,但尚未調(diào)用init函數(shù),因此是“半初始化”狀態(tài)。我們應(yīng)該修改對(duì)象(特別是析構(gòu)函數(shù))來(lái)處理這個(gè)新?tīng)顟B(tài)。這意味著,給每個(gè)方法添加新條件。

        有人可能想說(shuō),這還不是因?yàn)槟闳藶榈靥砑恿瞬皇褂卯惓5南拗??!如果?gòu)造函數(shù)中拋出異常,C++運(yùn)行時(shí)會(huì)正確地清理對(duì)象,不會(huì)出現(xiàn)“半初始化”狀態(tài)。

        話雖如此,然而問(wèn)題在于,如果使用異常,如上所述,就必須處理所有與異常相關(guān)的復(fù)雜性。對(duì)于一個(gè)需要在遇到故障時(shí)表現(xiàn)出優(yōu)秀的健壯性的基礎(chǔ)設(shè)施組件來(lái)說(shuō),這不是一個(gè)合理的選擇。

        此外,即使初始化沒(méi)有問(wèn)題,對(duì)象的銷(xiāo)毀也絕對(duì)會(huì)遇到問(wèn)題。你不能在析構(gòu)函數(shù)中拋出異常。這可不是我強(qiáng)加的人為限制,而是因?yàn)槿绻谶M(jìn)程中調(diào)用析構(gòu)函數(shù),或者恢復(fù)棧時(shí)恰好拋出異常,就會(huì)導(dǎo)致整個(gè)進(jìn)程崩潰。

        因此,如果銷(xiāo)毀可能失敗,你就需要兩個(gè)單獨(dú)的函數(shù)來(lái)處理它:

        class foo{public:...int term ();~foo ();};

        這就遇到了與初始化相同的問(wèn)題:一個(gè)“半終止”狀態(tài),我們必須以某種方式處理,向各個(gè)成員函數(shù)添加新條件。

        class foo{public:foo () : state (semi_initialised){...}int init (){if (state != semi_initialised)handle_state_error ();...state = intitialised;}int term (){if (state != initialised)handle_state_error ();...state = semi_terminated;}~foo (){if (state != semi_terminated)handle_state_error ();...}int bar (){if (state != initialised)handle_state_error ();...}};

        與之相比,C語(yǔ)言的代碼如下。其中只有兩種狀態(tài)。未初始化對(duì)象/內(nèi)存,我們無(wú)需擔(dān)心上述問(wèn)題,而且結(jié)構(gòu)可以包含任意數(shù)據(jù)。而且只要對(duì)象進(jìn)入已初始化的狀態(tài),就可以正常工作。因此,對(duì)象中不需要狀態(tài)機(jī):

        struct foo{...};int foo_init (){...}int foo_term (){...}int foo_bar (){...}

        考慮一下,如果在上述代碼中添加繼承,會(huì)發(fā)生什么。C++允許將基類(lèi)初始化為派生類(lèi)構(gòu)造函數(shù)的一部分。如果拋出異常,就會(huì)破壞已成功初始化的對(duì)象:

        class foo : public bar{public:foo () : bar () {}...};

        然而,一旦引入單獨(dú)的init函數(shù),狀態(tài)的數(shù)量就會(huì)開(kāi)始增長(zhǎng)。除了未初始化、半初始化、初始化和半終止?fàn)顟B(tài)之外,你還會(huì)遇到這些狀態(tài)的組合。你可以想象一個(gè)基類(lèi)已完全初始化、但派生類(lèi)半初始化的對(duì)象。

        對(duì)于這樣的對(duì)象,幾乎不可能確保其行為不出問(wèn)題。對(duì)象的半初始化和半終止部分有很多不同的組合,并且鑒于它們只在非常罕見(jiàn)的情況下才會(huì)引發(fā)故障,因此大多數(shù)相關(guān)代碼可能未經(jīng)測(cè)試就進(jìn)入了生產(chǎn)。

        綜上所述,我認(rèn)為,如果你的需求是不允許出現(xiàn)未定義的行為,則不適合面向?qū)ο蟮木幊?。這個(gè)問(wèn)題不僅限于C++,任何具有構(gòu)造函數(shù)和析構(gòu)函數(shù)的面向?qū)ο笳Z(yǔ)言都不適合。

        因此,更適合面向?qū)ο笳Z(yǔ)言的項(xiàng)目是:對(duì)開(kāi)發(fā)速度有要求、但對(duì)“不存在未定義的行為”沒(méi)有太高要求。

        這個(gè)問(wèn)題沒(méi)有靈丹妙藥。系統(tǒng)編程選擇C語(yǔ)言更為合適。

        本文轉(zhuǎn)自公眾號(hào)“CSDN”,ID:CSDNnews
        原文鏈接:https://250bpm.com/blog:4/?continueFlag=c778b3c9525d12a012a35269d830ebcc
        --- EOF ---

        瀏覽 48
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            男人色综合| 一级黄色大片乱伦 | 精品一区二区三区麻豆 | 地下偶像sana | 天天射天天射天天透天天干天天操 | 精品无码一区二区三区 | 深爱激情婷婷综合基地 | 男女操逼免费网站 | 亚洲娱乐在线 | 17c.c-起草精品蜜桃麻豆 |