• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿街逛

            嚴以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            C++ 編程指南

            版權所有 ? 1997 Rational Software Corporation。
            保留所有權利。

            “Rational”一詞和 Rational 產(chǎn)品名是 Rational Software Corporation 的商標。涉及到其他公司及其產(chǎn)品時將使用各自公司的商標且僅限于此目的。

            此文檔由來自 Calypso Software Inc. (Vancouver, B.C., Canada) 的 Luan Doan-Minh 為 Rational Software Corp. 編寫。


            目錄

            簡介

            基本原則
            前提
            指南分類
            最根本的原則

            代碼組織與風格

            代碼結(jié)構
            代碼風格

            注釋

            命名

            概述
            名字空間

            函數(shù)
            對象與函數(shù)參數(shù)
            異常
            其他事項

            聲明

            名字空間

            函數(shù)
            類型
            常量與對象

            表達式和語句

            表達式
            語句

            特殊主題

            內(nèi)存管理
            錯誤處理和異常

            可移植性

            路徑名
            數(shù)據(jù)表示
            類型轉(zhuǎn)換

            復用

            編譯問題

            指南總結(jié)

            要求或限制
            建議
            提示

            參考文獻


            第一章

            簡介

            大型軟件項目通常由相應的大型開發(fā)團隊承擔。大型團隊生成的代碼要有項目范圍內(nèi)可評測的質(zhì)量,代碼必須遵從于某一標準并以此來評價。因此,對大型的項目團隊來說,建立一個編程的標準或一組指南很重要。
            使用編程標準也使以下各項成為可能:

            • 增加開發(fā)過程代碼的強壯性、可讀性、易維護性;減少有經(jīng)驗和無經(jīng)驗開發(fā)人員編程所需的腦力工作;
            • 在項目范圍內(nèi)統(tǒng)一代碼風格;
            • 通過人為以及自動的方式對最終軟件應用質(zhì)量標準;
            • 使新的開發(fā)人員快速適應項目氛圍;
            • 支持項目資源的復用:允許開發(fā)人員從一個項目區(qū)域(或子項目團隊)移動到另一個,而不需要重新適應新的子項目團隊的氛圍。

            本文的目的是表述 C++ 編程的規(guī)則、指南和提示(通常也稱之為指南),它們可用來作為編程標準的基礎。對工作在大型項目團隊的軟件工程師,這些都是需要的。
            當前版本專注于程序的編制(雖然現(xiàn)在還很難在設計和編程間劃定明確的界限);以后將會添加設計指南。
            指南包括了以下 C++ 開發(fā)的方面:

            • 如何組織項目代碼;
            • 編程風格(如何編寫實際的源代碼);
            • 如何記錄源代碼;
            • 代碼內(nèi)名稱和源文件所使用的命名約定;
            • 何時使用某些語言結(jié)構以及何時應避免某些語言結(jié)構。

            它們從大量的行業(yè)知識中搜集而來。(請參見參考文獻:作者及參考文獻。)它們基于:

            • 廣為人知的軟件原理;
            • “好的”軟件實現(xiàn);
            • 所學課程;
            • 主觀意愿。

            大多會基于幾個第一種類型,也會基于大量的第二種和第三種類型。不幸的是,某些也基于最后一種類型;這主要是因為編程是一個高度主觀的活動:編程沒有普遍接受的最好或正確的萬靈藥。

            基本原則

            清晰、可理解的 C++ 源代碼是規(guī)則和指南的主要目標:清晰、可理解的源代碼是軟件可靠性和可維護性的主要作用因素。清晰、可理解的代碼可以表示為以下三個簡單的基礎原理 [Kruchten, 94]
            最小混淆 - 它的生存期中,源代碼的讀遠比寫多,規(guī)約更是這樣。理想情況下,源代碼讀起來應該象英語一樣描述了所要做的事,這同時還帶來了它執(zhí)行的好處。程序更多是為人編寫,而不是為計算機而編寫。閱讀代碼是一個復雜的腦力過程,它可由統(tǒng)一標準來簡化,在本文中還指最小混淆原則。整個項目中統(tǒng)一樣式是軟件開發(fā)團隊在編程標準上達成一致的主要原因,它不應視為一種懲罰或?qū)?chuàng)造性和生產(chǎn)力的阻礙。
            維護的唯一點 - 只要可能,設計決策就應在源中只表述一點,它的多數(shù)后果應程序化的派生于此點。不遵守這一原則嚴重損害了可維護性、可靠性和可理解性。
            最小干擾 - 最終,應用最小干擾原則(它是易讀性的主要作用因素)。即,避免將源代碼與可視干擾(如內(nèi)容較少或?qū)斫廛浖康牟黄鹱饔玫男畔ⅲ┫嗷旌希?br />指南這里所要表達的精神是不要過于苛刻;而對正確安全的使用語言特性提供指導。優(yōu)秀軟件的關鍵在于:
            了解每一個特性以及它的限制和潛在的危險;
            確切了解此特性可安全的使用于哪一個環(huán)境中;
            做出使用高度可視特性的決定;
            在合適的地方小心適度的使用特性。

            前提

            這里的指南只有少數(shù)幾個基本前提:
            讀者了解 C++。
            任何有益的地方都鼓勵使用 C++ 的高級特性,而不是只允許使用一些程序員都不熟悉的低級特性。這是項目能從使用 C++ 中獲益的唯一方式。C++ 不應只當成 C 來使用,事實上 C++ 的面向?qū)ο筇匦允顾粫?C 一樣使用。不鼓勵將代碼直譯成注釋;相反,在任何可能的地方應當使用源代碼代替注釋。
            遵從大型項目的做法。
            即使只是為了項目一級或公司一級的實現(xiàn)和統(tǒng)一,大型項目中許多規(guī)則仍很有價值,它們在小型系統(tǒng)中也有使用。
            代碼遵從于一個面向?qū)ο笤O計。
            許多規(guī)則都會支持從面向?qū)ο?(OO) 概念到 C++ 特性和具體命名約定的系統(tǒng)映射。

            指南分類

            指南具有不同的重要性;其重要性由以下標準來衡量:


            提示:
            以上符號所確定的指南只是一個小提示或小建議,即使忽略它也沒關系。
            建議:
            以上符號所確定的指南是一個建議,它通常是建立在更加技術性的基礎之上:封裝、內(nèi)聚、耦合、可移植性或可復用性與某些實現(xiàn)中的性能一樣都會受到影響。除非有合適的理由,否則必須遵從建議。

            要求或限制:
            以上符號確定的一個指南就是一個要求或限制;不遵從它肯定會導致壞的、不可靠的或不可移植的代碼。沒有棄權就不能違反要求或限制

            最根本的原則

            使用常識

            當無法找到可用的規(guī)則或指南時;當規(guī)則明顯不適用時;或當其他都已失敗時:使用常識,并核查基本原則。這條規(guī)則勝過其它所有的規(guī)則。即使存在規(guī)則或指南時也需要常識。


            第二章

            代碼組織與風格

            本章提供程序結(jié)構與層次的指導。

            代碼結(jié)構

            開發(fā)大型系統(tǒng)時通常將它分成許多小的功能子系統(tǒng)。子系統(tǒng)本身由許多代碼模塊組成。在 C++ 中,一個模塊通常包含了實現(xiàn)單一一個,少數(shù)情況下,也可能是一組緊密相關的抽象。C++ 中,一個抽象通常實現(xiàn)為一個。一個類有兩個不同的構件:一個是對類客戶可見的接口,它提供了類能力和責任的聲明或規(guī)約;另一個是所聲明規(guī)約(類定義)的實現(xiàn)
            與類相似,模塊也有它的接口和實現(xiàn):模塊接口包括對所包含模塊抽象(類聲明)的規(guī)約;模塊實現(xiàn)包括對抽象(類定義)的實際實現(xiàn)。
            在系統(tǒng)構造時,可將子系統(tǒng)組織成協(xié)作的組或?qū)觼頊p少或控制它們間的依賴性。

            不同文件中放置模塊規(guī)約與實現(xiàn)

            模塊的規(guī)約應放置在與模塊實施文件不同的文件中,此文件指頭文件。模塊的實施文件可能放置于一個或多個實施文件中。
            如果模塊實現(xiàn)包括擴展內(nèi)嵌函數(shù)、普通私有實現(xiàn)聲明、測試代碼或具體平臺的代碼,那就把這些部分分開存儲,并以那部分內(nèi)容來命名。
            如果程序的可執(zhí)行大小是一個要考慮的問題,則不常用到的函數(shù)應當置于它們各自的文件中。
            以以下方式構造一部分文件名:

            • 以模塊的主要抽象名作為模塊名。
            • 為模塊名附加一部分類型名。選擇暗示它們類型的部分類型名。
            • 模塊名和部分名由分隔符分隔(如“_”(下劃線)或“.”(句點));選擇一個分隔符,使用它要前后一致。

              File_Name::=

              <Module_Name> [<Separator> <Part_Name>] '.'<File_Extension>

            • 為了更好的預測,對文件名使用相同的大小寫,這與代碼內(nèi)名稱的使用相同。

            以下是模塊劃分與命名策略的示例:

            • module.inlines.cc - 如果一個模塊有多個潛在的內(nèi)嵌函數(shù),就將函數(shù)的定義置于一個單獨的內(nèi)嵌文件中(請參見“將模塊內(nèi)嵌函數(shù)定義置于單獨文件中”)。
            • module.private.hh - 如果模塊有許多常用的被其他部分引用的私有實現(xiàn)聲明,就把這些聲明分隔出去組成一個私有部分,它可由其他實施文件包含。
            • module.private.cc - 模塊的私有實施函數(shù)定義,為編輯的方便將它分離出去。
            • module.function_name.cc - 如果可執(zhí)行的大小是個要考慮的問題,應當分離出許多程序不需要的特殊成員函數(shù),組成各自的實施文件(請參見“如果程序大小需要考慮,將大型模塊分成多個變換單元”)。如果重載的函數(shù)置于不同文件中,每一文件函數(shù)名都應有一實例數(shù)字的后綴。如,function_name1 表示第一個獨立重載函數(shù)實例。
            • module.nested_class.cc - 模塊嵌套類的成員函數(shù),置于其本身文件中。
            • module.test.[hh\cc] - 如果一個模塊需要擴展測試代碼,則測試代碼必須在友測試類中予以聲明。友測試類應稱為Module.Test。將測試代碼聲明為友類有助于模塊及其測試代碼的獨立開發(fā);這還允許測試代碼從最終模塊對象代碼中刪除而源并不改變。
            • module.platform_name.cc - 分離出任意模塊的平臺依賴性并以平臺名稱命名部分名稱(請參見“分離平臺依賴性”)。

            選擇一個模塊劃分和命名的機制,使用它要前后一致。

            示例
            SymaNetwork.hh         //包括類
                                   // “SymaNetwork”的聲明。
            SymaNetwork.Inlines.cc //內(nèi)嵌定義子單元
            SymaNetwork.cc         //模塊的主要實施單元
            SymaNetwork.Private.cc //私有實施子單元
            SymaNetwork.Test.cc    //測試代碼子單元
            基本原理

            從模塊的實施中分離出其規(guī)約有助于用戶和提供者代碼的獨立開發(fā)。
            將一個模塊的實施分割成多個變換單元,這可以更好的支持刪除對象代碼,并會導致更小的可執(zhí)行大小。
            使用規(guī)范化的文件命名與劃分約定允許在不檢查模塊的實際內(nèi)容時理解其內(nèi)容與組織。
            將名稱從代碼讓渡給文件名增加了可預測性,有助于建立基于文件的工具而不需要復雜命名映射 [Ellemtel, 1993]

            只選擇一組文件擴展名以區(qū)分頭文件和實施文件

            常用的文件擴展名是:對頭文件,為.h、.H、.hh、.hpp.hxx;對實施文件,為.c、.C、.cc、.cpp和.cxx。選擇一組擴展名,使用它要前后一致。

            示例
            SymaNetwork.hh  //擴展名“.hh”用來指定
                            //一個“SymaNetwork”模塊的頭文件。
            SymaNetwork.cc  //擴展名“.cc”用來指定
                            //一個“SymaNetwork”模塊的實施文件
            注意

            對由名字空間封裝的頭文件,C++ 草擬標準工作底稿也使用擴展名“.ns”。

            每個模塊規(guī)約避免定義多個類


            只有在極少數(shù)情況下多個類才能置于同一個模塊中;此時他們必須是緊密關聯(lián)關系的(如容器與它的迭代程序)如果所有類都需要對客戶模塊是可見的,可以把模塊的主要類及其支持類置于同一個頭文件中。

            基本原理

            減少模塊的接口以及其他模塊對此接口的依賴。

            避免將私有實施聲明置于模塊的規(guī)約中

            除了類的私有成員以外,模塊的私有實施聲明(如實施類型和支持類)不能出現(xiàn)在模塊的規(guī)約中。除非多個實施文件都需要這些聲明,否則它們應當置于所需的實施文件中;如果有多個實施文件,則這些聲明應置于輔助私有頭文件中。而其他實施文件在需要時應包含這個輔助私有頭文件。
            這一做法保證了:
            模塊規(guī)約清楚的表示了它的抽象,不再需要實施工件;
            模塊規(guī)約越小越好,并因此減小了模塊間的編譯依賴(請參見“減小編譯依賴”);

            示例
            //模塊 foo 的規(guī)約,包含于文件“foo.hh”中;
            //
            class foo
            {
            .. 聲明
            };
            //“foo.hh”結(jié)束;
            //模塊 foo 的私有聲明,包含于文件
            // “foo.private.hh”中;所有 foo 實施文件都使用它。
            ... 私有聲明
            //“foo.private.hh”結(jié)束;
            //模塊 foo 的實施,包含于以下多個文件中
            // “foo.x.cc”和“foo.y.cc”
            //文件“foo.x.cc”;
            //
            #include "foo.hh" //包含模塊本身的頭文件
            #include "foo.private.hh" //包含實施
            //所需的聲明。
            ... 定義
            //“foo.x.cc”結(jié)束
            //文件“foo.y.cc”;
            //
            #include "foo.hh"
            #include "foo.private.hh"
            ... 定義
            //“foo.y.cc”結(jié)束

            通常使用 #include 來訪問模塊的規(guī)約

            模塊要使用另一模塊,則必須使用預處理 #include 指令來訪問提供者模塊的規(guī)約。相應的,模塊不能再次聲明提供者模塊規(guī)約的任何一部分。
            當包含文件時,只有對“標準”頭文件使用 #include <header> 語法;對其余的使用 #include "header" 語法。
            使用 #include 指令也適用于模塊自身的實施文件:模塊實施文件必須包括它本身的規(guī)約和私有輔助頭文件(請參見“不同文件中放置模塊規(guī)約與實施”);

            示例
            //模塊 foo 頭文件中的規(guī)約
            // "foo.hh"
            //
            class foo
            {
            ... 聲明
            };
            //“foo.hh”結(jié)束;
            
            //模塊 foo 在文件“foo.cc”中的實施;
            //
            #include "foo.hh" // 實施文件包含
            //它本身的規(guī)約
            ... foo 成員的定義
            //“foo.cc”結(jié)束

            #include 規(guī)則的一個例外是當模塊只通過引用(使用指針或引用類型聲明)使用或包含提供者模塊的類型(類);這種情況下引用或包含通過使用預先聲明來規(guī)約(請參見“減小編譯依賴”),而不使用 #include 指令。
            只包含絕對需要的文件:這意味著模塊頭文件不應包含只有模塊實施需要的其他頭文件。

            示例
            #include "a_supplier.hh"
            
            class needed_only_by_reference;//對只需要
            		            //指針或引用的類
                                          //使用
                                          //預先聲明來訪問
            void operation_requiring_object(a_supplier required_supplier, ...);
            //
            //操作需要一個實際的提供者對象;
            //提供者規(guī)約必須已有 #include 指令。
            
            void some_operation(needed_only_by_reference& a_reference, ...);
            //
            //某些操作只需要對對象進行引用;
            //因此為提供者使用預先聲明。
            基本原理

            這一規(guī)則保證了:

            • 只存在唯一的模塊接口聲明,所有的客戶只看到同一接口;
            • 模塊間的編譯依賴最大程度的減小了;
            • 客戶不會為代碼無故產(chǎn)生不需要的編譯開支。

            將模塊內(nèi)嵌函數(shù)的定義置于單獨的文件中

            當模塊有許多內(nèi)嵌函數(shù)時,它們的定義應置于一個分離的只有內(nèi)嵌函數(shù)的文件中。在模塊頭文件的末尾應包含內(nèi)嵌函數(shù)文件。
            請參見“使用 No_Inline 條件編譯符破壞內(nèi)嵌編譯”。

            基本原理

            這種技術使實施細節(jié)不會聚集到模塊頭文件中;因此,保留了一個清晰的規(guī)約。當不進內(nèi)嵌編譯時,它也有助于減少代碼復制:使用條件編譯,可將內(nèi)嵌函數(shù)編譯成一個對象文件而不是靜態(tài)的編譯成每一個使用的模塊。相應的,內(nèi)嵌函數(shù)定義不應在類定義中定義,除非它們非常瑣碎。

            如果程序規(guī)模是個要求考慮的問題,就把大的模塊分割成多個變換單元

            將大型模塊分割成多個變換單元有助于在程序鏈接中刪除未經(jīng)引用的代碼。很少引用的成員函數(shù)應當分割成獨立文件,與經(jīng)常使用的函數(shù)相分離。最后,各成員函數(shù)可置于他們自己的文件中 [Ellemtel, 1993]

            基本原理

            鏈接器在對象文件中消除無引用代碼的能力各不相同。將大型模塊分割成多個文件允許這些鏈接器通過消除整個對象文件中的鏈接來減少可執(zhí)行大小 [Ellemtel, 1993]

            注意

            先考慮模塊是否能分割成小的抽象,這也是很值得的。

            分離平臺依賴性

            從獨立于平臺的代碼中分離出依賴于平臺的代碼;這有助于提高可移植性。依賴于平臺的模塊應當具有受平臺名限制的文件名以突出對平臺的依賴。

            示例
            SymaLowLevelStuff.hh         //“LowLevelStuff”
                                         //規(guī)約
            SymaLowLevelStuff.SunOS54.cc // SunOS 5.4 實施
            SymaLowLevelStuff.HPUX.cc    // HP-UX 實施
            SymaLowLevelStuff.AIX.cc     // AIX 實施
            注意

            從構架與維護角度,將對平臺的依賴包含在少數(shù)幾個低層子系統(tǒng)中也是一個好方法。
            采納一個標準文件內(nèi)容結(jié)構,使用它要前后一致。
            建議文件內(nèi)容就構包括以下次序的以下部分:

              1. 重復包含保護(只適用于規(guī)約)。
              2. 確定可選文件與版本控制。
              3. 本單元所需包含的文件。
              4. 模塊紀錄(只適用于規(guī)約)。
              5. 聲明(類、類型、常量、對象、函數(shù))和其他文本規(guī)約(前置及后置條件,常量)。
              6. 對這種模塊內(nèi)嵌函數(shù)定義的包含。
              7. 定義(對象與函數(shù))和私有實施聲明。
              8. 版權聲明。
              9. 可選版本控制歷史。

            基本原理

            以上文件的內(nèi)容次序,首先呈現(xiàn)給了客戶相關的信息;這與安排類的公共、保護和私有部分次序的基本原理相一致。

            注意

            根據(jù)公司的政策,版權信息應置于文件的首部。

            防止重復文件包含的保護

            通過在每個頭文件中使用以下結(jié)構可以防止重復包含與編譯文件:

            #if !defined(module_name) //使用預處理符
            #define module_name       //防止重復
                                      //包含...//聲明到此
            #include "module_name.inlines.cc" //可選的內(nèi)嵌結(jié)構
                                              //包含到此。
            //包含模塊內(nèi)嵌函數(shù)之后
            //沒有其他聲明。
            #endif //module_name.hh 結(jié)束

            對包含保護符使用模塊文件名。對模塊名與操作符使用同一大小寫。

            使用"No_Inline"條件編譯符破壞內(nèi)嵌編譯

            使用以下條件編譯結(jié)構控制可內(nèi)嵌函數(shù)的內(nèi)嵌(相對于外部的)編譯。

            //在文件 module_name.inlines.hh 首部,
            #if !defined(module_name_inlines)
            #define module_name_inlines
            
            #if defined(No_Inline)
            #define inline //使內(nèi)嵌關鍵詞無效
            #endif
            
            ... //內(nèi)嵌定義到此
            #endif //文件 module_name.inlines.hh 結(jié)束
            
            //文件 module_name.hh 尾
            //
            #if !defined(No_Inline)
            #include "module_name.inlines.hh"
            #endif
            
            //包含module_name.hh之后
            // module_name.cc 文件首
            //
            #if defined(No_Inline)
            #include "module_name.inlines.hh"
            #endif

            條件編譯結(jié)構與多包含保護結(jié)構相似。如果未定義 No_Inline,則內(nèi)嵌函數(shù)與模塊規(guī)約一同編譯并自動從模塊實施中排除出去。如果定義了 No_Inline,則模塊規(guī)約不包含內(nèi)嵌定義,但廢除 inline 關鍵詞的模塊實施包含了它。

            基本原理

            以上技術允許內(nèi)嵌函數(shù)在外部編譯時的精簡代碼復制。使用條件編譯,內(nèi)嵌函數(shù)的簡單復制編譯成了定義模塊;而當由編譯器開關規(guī)約外部編譯時,所復制的代碼在每個使用的模塊里編譯成“靜態(tài)”(內(nèi)部鏈接)函數(shù)。

            注意

            條件編譯增加了維護構建依賴關系的復雜性。對這種復雜性的管理通常是利用把頭文件和內(nèi)嵌函數(shù)定義視為一個邏輯單元:實施文件因此依賴于頭文件以及內(nèi)嵌函數(shù)定義文件。

            代碼風格

            對嵌套語句使用小的、一致的縮進風格

            使用一致縮進來清楚的界定嵌套語句;為達到這一目的,兩到四個空格的縮進是最有效的方式。我們推薦使用規(guī)則的兩空格縮進。
            混合語句或塊語句的分界符 ({}),應當與周圍的語句處于縮進的同一級上(這意味著垂直布置{})。通過選擇空格的數(shù)量對塊內(nèi)的語句進行縮進。
            switch 語句的 case 標號應當與 switch 語句處于同一縮進級;switch 語句內(nèi)語句應比 switch 語句本身和 case 標號多縮進一級。

            示例
            if (true)
            {  		//新程序塊
            foo(); //程序塊內(nèi)語句
                   //縮進兩個空格
            }
            else
            {
            bar();
            }
            while (expression)
            {
            statement();
            }
            switch (i)
            {
            case 1:
            do_something();//相對 switch 語句本身
                           //縮進
            break;         //一層
            case 2:
            //...
            default:
            //...
            }            
            基本原理

            能夠輕易識別程序塊,還要在不超出顯示屏或打印頁面界限的前提下有足夠多的嵌套,使用兩空格的縮進是一個折衷的方案。

            相對于函數(shù)名或作用域名縮進函數(shù)參數(shù)

            如果一行之內(nèi)難以容下函數(shù)聲明,則將函數(shù)的第一個參數(shù)放在函數(shù)名所在的那一行;其余參數(shù)每個占一新行,使用與第一個參數(shù)相同的縮進。下面所示的聲明與縮進的風格,在函數(shù)返回類型和名稱下留有空格;因此,提高了它們的可見性。

            示例
            void foo::function_decl( some_type first_parameter, 
            some_other_type second_parameter,
            status_type and_subsequent);

            如果按照以上的指南會導致?lián)Q行,或參數(shù)縮進的太多,則以函數(shù)名或作用域名(類、名字空間)縮進所有的參數(shù),每一參數(shù)獨占一行:

            示例
            void foo::function_with_a_long_name( //函數(shù)名是
                                                 //不易看到的
                                                 some_type first_parameter, 
                                                 some_other_type second_parameter,
                                                 status_type and_subsequent);

            請參照下面的布置規(guī)則。

            使用能夠適合標準打印紙大小的最大行寬

            應限制程序行的寬度以防止在使用標準(信紙)或缺省打印紙張進行打印時丟失信息。

            注意

            如果縮進使深層的嵌套語句處于太右邊,語句又太長以致超出了右邊的空白,這時就應當考慮將代碼分成更小的更易管理的函數(shù)。

            使用一致?lián)Q行

            當函數(shù)聲明、定義、調(diào)用或 enum 聲明中枚舉操作符的參數(shù)列表不能置于一行,則將每一個列表元素置于單獨的一行(請參見“相對函數(shù)名和作用域縮進函數(shù)參數(shù)”)。

            示例
            enum color { red, 
            orange, 
            yellow, 
            green, 
                         //...
            violet
                               };            

            如果一個類或函數(shù)模板聲明太長,則在模板的實參列表后進行連續(xù)換行。例如(標準迭代程序庫的聲明,[X3J16, 95]):

            template <class InputIterator, class Distance>
            void advance(InputIterator& i, Distance n);



            第三章

            注釋

            本章對代碼注釋的使用提供指導。
            注釋應當作為源代碼的補充,而不是直譯源代碼:

            • 它們應當解釋不能直接從源代碼看出東西;它們不應復制語言的語法或語義。
            • 它們應當幫助讀者掌握背景中的概念、依賴性、特別是復雜的數(shù)據(jù)代碼和算法。
            • 它們應當突出:與代碼或設計標準的不同點、受限特性的使用、以及特殊的“技巧”。

            對每一行注釋,程序員都應能夠輕松回答:這一注釋有何價值?通常,合理選擇名稱就可不要注釋。除非注釋出現(xiàn)在正規(guī)的程序設計語言中 (PDL),否則編譯器不對其進行編譯;因此,根據(jù)單點維護原則,應當以源代碼的方式表達設計決策,而不是以注釋的方式,即使這樣需要更多的聲明。

            使用 C++ 風格的注釋而非 C 風格的注釋

            應使用 C++ 風格注釋分界符"//",而非 C 風格的"/*...*/"。

            基本原理

            C++ 風格的注釋更易理解,它減少了由于偶然缺少一個注釋結(jié)束分隔符而造成大量代碼被注釋掉的風險。

            反例
            /*注釋開始,但缺少注釋結(jié)束分隔符
            do_something();
            do_something_else(); /*對 do_something_else 的注釋*/
                                      //注釋到此結(jié)束。
                                      // do_something 和
                                      // do_something_else
                                      //都意外的被注釋掉了!
            Do_further();

            注釋與源代碼盡可能靠攏

            注釋應緊貼它們所要注釋的代碼;它們使用相同的縮進,使用一個空注釋行接于代碼之后。
            對多行連續(xù)語句的注釋應置于語句的上方作為語句的介紹性說明。而對單個語句的注釋應置于語句的下方

            示例
            //置于語句前的注釋
            //它對下面多個語句進行注釋
            // 
            ...
            void function();
            //
            //語句之后的注釋
            //它對前一個語句進行注釋。

            避免行末注釋

            注釋應避免與源結(jié)構處于同一行:否則會使注釋與其所注釋的源代碼不對齊。但在描述長聲明中的元素(如 enum 聲明中的枚舉操作符)時,也能容忍這種不好的注釋方式。

            避免注釋頭

            避免使用包含作者、電話號碼、創(chuàng)建和修改日期的注釋頭:作者和電話號碼很快就過時了;而創(chuàng)建和修改日期以及修改的原因則最好由配置管理工具來維護(或其他形式的版本歷史文件)。
            即使對主結(jié)構(如函數(shù)和類),也要避免使用垂直滾動條,閉合的欄或框。它們搞亂了視覺效果,這樣就很難保持一致性。使用空行而不是多個注釋行來分割相關的源代碼塊。使用一個空行來分離函數(shù)或類中的結(jié)構。使用兩個空行將函數(shù)與函數(shù)相分離。
            框架或表單看起來具有較好的一致性,還提醒了程序員來注釋代碼,但它們會導致直譯的風格 [Kruchten, 94]

            使用空注釋行分離注釋段

            在一個注釋塊中,使用空注釋行而非空行來分割注釋段

            示例
            //在下一段中
            //需要繼續(xù)這里的解釋
            //
            //空注釋行使得
            //同一注釋塊中
            //不同段間的界限分明。

            避免冗余

            注釋中應避免重復程序標識符,避免復制別處有的信息(此時可使用一個指向信息的指針)。否則程序中的任何一處改動都可能需要多處進行相應的變動。如果其他地方?jīng)]有進行所需的注釋改動,將會導致誤注釋:這種結(jié)果比根本沒有注釋還要糟糕。

            編寫自記錄代碼而非注釋

            時刻注意要編寫自記錄代碼而非注釋。這可通過選擇合理的名稱、使用特殊的臨時變量或重新安排代碼的結(jié)構來實施。注意注釋中的風格、語法和拼寫。使用自然語言注釋而不是電報或加密格式。

            示例
            替換如下代碼:
            do
            {
            ...
            } while (string_utility.locate(ch, str) != 0); 
            //當找到時退出查找循環(huán)。
            將以上代碼改寫成:
            do
            {
            ...
            found_it = (string_utility.locate(ch, str) == 0);
            } while (!found_it);

            記錄類與函數(shù)

            雖然自記錄代碼比注釋好;但通常還需要提供一些超出解釋代碼復雜部分以外的信息。至少需要記錄以下信息:

            • 每個類的目的;
            • 函數(shù)名不能清楚說明它的目的時,則記錄函數(shù)相應的目的;
            • 返回值的含義;如不可預測函數(shù)布爾返回值的含義:如,ture 值是否表示函數(shù)執(zhí)行成功;
            • 出現(xiàn)異常的條件;
            • 如果有的話,參數(shù)的前置或后置條件;
            • 其他數(shù)據(jù)訪問,特別是當數(shù)據(jù)被修改時:對有副作用的函數(shù)特別重要;
            • 合理使用類或函數(shù)所需的限制或其他信息;
            • 對類型和對象來說,語言無法表達的任何不變量或其他限制。
            基本原理

            與聲明相結(jié)合的代碼記錄應當足以使客戶使用代碼;因為只使用 C++ 不能完全表達類、函數(shù)、類型和對象的完整語義,所以需要記錄存檔。


            第四章

            命名

            本章為不同 C++ 實體命名指南。

            概述

            為程序?qū)嶓w(類、函數(shù)、類型、對象、常量、異常、名字空間)取一個好名稱并不是一件容易的事。對中大型的應用程序來說,問題就顯得更加困難:這里名稱容易沖突,并且還缺少一些近義詞來區(qū)分相似但不相同的概念,這就更增加了困難的程度。
            使用名稱約定可減少創(chuàng)造合適名稱時的腦力勞動。除了這一好處以外,名稱約定還帶來了代碼的一致性。一個有用的名稱約定必須在以下方面提供向?qū)В号虐骘L格(或如何書寫名稱)以及名稱的構建(或如何選擇名稱)。

            選擇一個名稱約定,使用它要前后一致

            只要能夠前后一致,使用哪一個名稱約定并不重要。命名的一致性比實際的約定重要的多:一致性支持“最小混淆”原則。
            因為 C++ 是一個區(qū)分大小寫的語言,并且在 C++ 應用界有多種不同的命名約定,幾乎不可能實現(xiàn)命名的絕對一致。我們推薦基于主機的環(huán)境(如 UNIX 或 Windows)以及項目所使用的主要的庫選擇項目的命名約定;盡可能實現(xiàn)代碼的一致性:

            • 主機使用 UNIX 且不常使用商業(yè)庫(如 X Window 庫、X Toolkit Intrinsics 和 Motif)的項目可能傾向于使用全部為小寫字符、以下劃線分隔的約定:這是 UNIX 系統(tǒng)調(diào)用以及 C++ 草擬標準工作底稿使用的約定。
            • 以商業(yè)庫為中心的 UNIX 主機項目可能更傾向于使用大寫風格,通常也稱為 Smalltalk 風格 - 一種首字母大寫,字間直接相連而無分隔符的書寫風格。
            • Microsoft 基于 Windows 的項目可能會推薦使用并不常用的 Microsoft? “匈牙利”命名法我們不推薦使用這種命名風格,因為它違背了本文指南的基本原則。
            注意:

            細心的讀者會發(fā)現(xiàn)本文的示例現(xiàn)在并未遵循所有的指南。這部分是因為示例的來源有多個,還因為希望減少篇幅。因此,沒有嚴格遵照格式化的指南來做。但需聲明:照我說的去做,不要學我的實際做法。

            永遠不要聲明以一個或多個下劃線 ('_') 開頭的名稱

            開頭帶有一個下劃線(“_”)的名稱通常由庫函數(shù)(“_main”和“_exit”)使用。開頭帶有兩個下劃線(“__”)或一個下劃線后接一個大寫字母的名稱保留給編譯器內(nèi)部使用。
            名稱還要避免下劃線相鄰,否則很難識別下劃線的確切個數(shù)。

            避免使用只靠字母大小寫才能區(qū)分的名稱

            只通過大小寫才能區(qū)分的類型名稱,它們間的差異是很難記憶的,也就很容易造成混淆。

            避免使用縮寫

            應用領域中常用的縮寫(如 FFT 指快速傅立葉變換),或在項目縮寫清單中有定義的縮寫,才能使用相應的縮寫。否則,很有可能相似但不相同的縮寫到處出現(xiàn),之后就引進了混淆和錯誤(如將 track_identification 縮寫成 trid、trck_id、tr_iden、tid、tr_ident等)。

            避免使用后綴來表明程序語言結(jié)構

            使用后綴劃分實體種類(如對type(類型)使用 type,對 exceptions(異常)使用 error)對幫助理解代碼通常并不有效。后綴如 arraystruct 還意味著一個具體的實現(xiàn);在實現(xiàn)改變時(結(jié)構或數(shù)組表示的改變)對客戶代碼要么會有反面的結(jié)果,要么會起誤導的作用。
            但后綴在有限幾種情況下也會有一定用處:

            • 當可供選擇的標識符非常有限時,選擇一個最佳名稱并用后綴表明它的類型。
            • 當它表示一個應用領域的概念如 aircraft_type(飛行器類型)時。

            選擇清晰的、易辨認的、有意義的名稱

            從應用的角度選擇名稱,名詞使用形容詞修飾來提高局部(具體上下文)的含義。確保名稱與其類型保持一致。
            選擇合適的名稱,使以下結(jié)構:

            object_name.function_name(...);
            object_name->function_name(...);

            易于閱讀并有實際含義。
            短名稱或縮寫名稱打字速度快不是使用它們的可以接受的理由。單字母或很短幾個字母的標識符通常出于選擇不當或懶惰。但使用人們熟知的 E 作為自然對數(shù)的底數(shù)或 Pi 作為圓周率就是例外了。
            不幸的是,編譯器和支持的工具有時限制名稱的長度。因此,應當注意:長名稱不應只在結(jié)尾的幾個字符有所不同,因為結(jié)尾的幾個用于區(qū)分的字符可能會被這些工具截斷。

            示例
            void set_color(color new_color)
            {
            ...
            the_color = new_color;
            ...
            }
            優(yōu)于
            void set_foreground_color(color fg)
            和:
            oid set_foreground_color(color foreground);{
            ...
            the_foreground_color = foreground;
            ...
            }      

            第一個示例中名稱的選擇要優(yōu)于其它兩個:對 new_color 進行了限定使它與其類型一致;因此增強了函數(shù)的語義。
            第二個示例中,讀者直覺上會認為 fg 意指 foreground(前景);但是,任何一個好的編程風格都不應留給讀者作直覺推導的余地。
            第三個示例中,當使用參數(shù) foreground(遠離其聲明)時,讀者會認為 foreground 實際上就是指 foreground 的顏色。可以想象,它能代表任何一種可暗中轉(zhuǎn)化為 color 的類型。

            注意:

            使用名詞和形容詞構成名稱、確保名稱與類型相符、遵循自然語言以增強代碼的可讀性和語義。

            使用名稱的正確拼寫

            英語字符名稱部分應正確的拼寫,遵守項目要求的形式,如使用一致的英國英語或美國英語,但不能同時使用。這對注釋也同樣適用。

            布爾值使用正值謂詞從句

            對布爾值類型的對象、函數(shù)及函數(shù)實參使用正值形式的判定句式,如 found_itis_available,但不使用 is_not_available

            基本原理

            當否定謂詞時,雙重否定更難以理解。

            名字空間

            使用名字空間由子系統(tǒng)或庫劃分潛在全局名稱

            如果將系統(tǒng)解構成子系統(tǒng),使用子系統(tǒng)名稱作為名字空間的名稱,這樣可劃分系統(tǒng)的全局名字空間并使其最小化。如果系統(tǒng)是一個庫,對整個庫使用一個最外層的名字空間。
            為每一個子系統(tǒng)或庫名字空間取一個有意義的名稱;此外,為它取一個簡化的或縮寫的別名。選擇不會引起沖突的簡寫或縮寫的別名,如 ANSI C++ draft standard library(ANSI C++ 草擬標準庫)[Plauger, 95]std 定義為 iso_standard_library 的別名。
            如果編譯器尚未支持名字空間結(jié)構,使用名稱前綴模擬名字空間。例如,系統(tǒng)管理子系統(tǒng)接口中的公共名稱應帶有前綴 syma (System Management(系統(tǒng)管理)的簡寫)。

            基本原理

            使用名字空間包含可能的全局名稱,這樣有助于子項目團隊或廠商獨立開發(fā)代碼時避免名稱沖突。這必然導致只有名字空間是全局的。

            類的名稱使用名詞或名詞短語

            使用簡單形式的常用名詞或名詞短語,為類取名時要表達出它的抽象含義。基類使用更通用的名稱,而對派生類使用更專用的名稱。

            typedef ... reference; //出自標準庫
            typedef ... pointer;   //出自標準庫
            typedef ... iterator;  //出自標準庫
            class bank_account {...};
            class savings_account : public bank_account {...};
            class checking_account : public bank_account {...};

            當對象和類型的名稱沖突或缺少合適的名稱時,對象使用簡單名稱,類型名稱添加后綴 mode、kind、code 等。
            表明是對對象集合的抽象時使用復數(shù)形式。

            typedef some_container<...>yellow_pages;

            當需要對象集合外的其他語義時,使用以下標準庫中的數(shù)據(jù)結(jié)構作為行為模式和名稱后綴:

            • vector(向量) - 一種可隨機訪問的順序容器;
            • list(表) - 一種有序的順序容器;
            • queue(隊列) - 一種先入先出的順序容器;
            • deque(雙端隊列) - 一種兩個端口都可進行操作的隊列;
            • stack(堆棧) - 一種后入先出的順序容器;
            • set(集合) - 一種關鍵詞訪問(關聯(lián)關系)容器;
            • map(圖) - 一種關鍵詞訪問(關聯(lián)關系)容器;

            函數(shù)

            過程類型的函數(shù)名稱使用動詞

            對無返回值的函數(shù)(函數(shù)聲明為 void 返回類型)或返回值使用指針或引用類型的函數(shù),使用動詞或動詞短語。
            對使用非 void 返回類型返回唯一一個值的函數(shù),使用名詞或名詞短語。
            對帶有常用操作(行為模式)的類,使用項目選項列表中的操作名。例如:begin, end, insert, erase (標準庫中的容器操作)。
            避免使用“get”和“set”來命名表明狀態(tài)的函數(shù)(給函數(shù)添加前綴“get”和“set”),特別是對取出和設置對象屬性的公共操作更是這樣。操作命名應當停留在類抽象和服務提供一級上;取出和設置對象屬性是低層次的實現(xiàn)細節(jié),如果使它們公共化,則會降低封裝的完整性。
            返回布爾值(謂詞)的函數(shù)使用形容詞(或過去分詞)。謂詞經(jīng)常使用名詞前添加前綴 ishas 的形式使名稱表達一個肯定的聲明。當對象、類型名或枚舉型常量也使用簡單名稱時,這也同樣非常有用。時態(tài)上要保持一致,力求準確。

            示例
            void insert(...);
            void erase(...);
            
            Name first_name();
            bool has_first_name();
            bool is_found();
            bool is_available();

            不要使用否定意義的名稱,因為這會導致雙重否定表達式出現(xiàn)(如 !is_not_found);這使得代碼更難以理解。有些情況下,通過使用反義詞,如“is_invalid”代替“is_not_valid”,可以使否定謂詞變成肯定的而不需改變其語義。

            示例
            bool is_not_valid(...);
            void find_client(name with_the_name, bool& not_found);
            Should be re-defined as:
            bool is_valid(...);
            void find_client(name with_the_name, bool& found);

            當需要使用同一通用含義時,使用函數(shù)的重載

            當操作目的相同時,使用重載而不使用同義詞;這盡量減少了系統(tǒng)中概念的數(shù)量和不同的操作,因此也就降低了整體的復雜性。
            當重載操作符時,要確保操作符的語義保留了下來;如果不能保留約定俗成的操作符含義,則為函數(shù)選用另一個名稱而不使用操作符重載方式。

            對象與函數(shù)參數(shù)

            實參名使用語法元素強調(diào)其含義

            為表明其唯一性或表明本實體是活動的主要核心,在對象或參數(shù)名稱前加前綴“the”或“this”。要表明次要、臨時、輔助對象,則加前綴“a”或“current”:

            示例
            void change_name( subscriber& the_subscriber,
            const subscriber::name new_name)
            {
            ...
            the_subscriber.name = new_name;
            ...
            }
            void update(subscriber_list& the_list,
            const subscriber::identification with_id,
            structure& on_structure,
            const value for_value);
            void change( object& the_object,
            const object using_object);

            異常


            異常名選用否定的含義

            只有在處理錯誤時才必須使用異常,故使用名詞或名詞短語來清楚的表達一個否定的含義:

            overflow, threshold_exceeded, bad_initial_value

            異常名使用項目定義過的形容詞

            使用項目定義列表中以下詞的一個 bad、incomplete、invalid、wrong、missingillegal 作為名稱的一部分,而不要機械的套用 errorexception,因為它們并不表達具體的信息。

            其他事項

            浮點指數(shù)和十六進制數(shù)使用大寫字母。

            浮點數(shù)中的字母“E”和十六進制數(shù)從“A”到“F”應當一直保持大寫。


            第五章

            聲明

            本章為不同 C++ 聲明類型的使用及形式提供指南。

            名字空間

            C++ 語言中名字空間存在之前,管理名稱的作用域只有有限的幾種手段;因此,全局名字空間的使用過于泛濫,導致眾多的沖突,這使同一程序中難以同時使用一些庫。新的名字空間語言特征解決了全局名字空間的干擾問題。

            將全局聲明限定在名字空間中

            這意味著只有名字空間的名稱可以是全局的;所有其他的聲明都必須在某個名字空間的作用域內(nèi)。
            忽略了這一規(guī)則可能最終導致名稱沖突。

            使用名字空間劃分非類功能

            邏輯上劃分非類功能類的類別,或作用域遠大于類的功能(如庫或子系統(tǒng)),使用名字空間邏輯上統(tǒng)一的聲明(請參見“使用名字空間由系統(tǒng)或庫劃分潛在的全局名稱”)。
            名稱表達了功能的邏輯劃分。

            示例
            namespace transport_layer_interface { /* ...*/ };
            namespace math_definitions { /* ...*/ };      

            盡量不使用全局和名字空間范圍的數(shù)據(jù)。

            使用全局和名字空間范圍內(nèi)數(shù)據(jù)與封裝原則是背道而馳的。

            C++ 中類是基本的設計實施單元。使用它們紀錄域與設計的抽象,并作為實施抽象數(shù)據(jù)類型 (ADT) 的封裝機制。

            使用 class 而不是 struct 來實現(xiàn)抽象數(shù)據(jù)類型

            使用 class(類的關鍵字)而不是 struct 來實現(xiàn)一個表示抽象數(shù)據(jù)類型的類。
            使用 struct(類的關鍵字)來定義類似 C 中的舊式數(shù)據(jù)結(jié)構 (POD),尤其是在與 C 代碼程序接口時。
            雖然 classstruct 是等價的,可以互換,但 class 具有更好的默認訪問控制 (private),這樣就提供了更好的封裝性能。

            基本原理

            區(qū)分 classstruct 的的一致做法引入了以上的語義差別,并且還超出了語言規(guī)則的范圍:class 是紀錄抽象與封裝最先考慮的結(jié)構;而 struct 代表一個純數(shù)據(jù)結(jié)構,它可以在混合編程語言程序中交換使用。

            以可訪問權限逐次降低的順序聲明類的成員

            類聲明中的訪問限定符應以順序 public, protected, private 出現(xiàn)。

            基本原理

            以 public、protected、private 順序排列的成員聲明保證了類用戶最感興趣的信息置于最前列,因此減少了類用戶訪問不相關信息或?qū)崿F(xiàn)細節(jié)。

            抽象數(shù)據(jù)類型避免聲明公共或保護數(shù)據(jù)成員

            公共或保護數(shù)據(jù)成員的使用降低了類的封裝性并影響了系統(tǒng)抵抗變化的能力:公共數(shù)據(jù)成員將類的實現(xiàn)暴露給了它的用戶;保護數(shù)據(jù)成員將類的實現(xiàn)暴露給了從它繼承的類。類公共數(shù)據(jù)成員或保護數(shù)據(jù)成員的任何改變都會影響到用戶和繼承類。

            使用友元保留封裝性

            初次碰到時,它好像是違反直覺的:友元關系將一個類的私有部分暴露給了它的友元,還如何實現(xiàn)封裝呢?當類之間高度相互依賴,需要相互間的內(nèi)部信息時,最好是賦予它們間友元關系而不是通過類的接口將其內(nèi)部細節(jié)暴露出來。
            不希望如同公共成員一樣暴露類的內(nèi)部細節(jié)以提供給類客戶訪問權。將保護成員暴露給潛在的繼承類,鼓勵了分級設計,但這也是不希望的。友元關系有選擇性的賦予了對私有成員的訪問權,而不用實現(xiàn)子類限制。因此再所需訪問之外保留了封裝。
            將友元關系賦予友元測試類很好的說明了友元關系保留了封裝特性。友元測試類通過訪問類的內(nèi)部可完成相應的測試代碼,之后,友元測試類可從交付的代碼中刪除。因此,沒有丟失封裝,也沒有向可交付代碼增加代碼。

            避免在類聲明中定義函數(shù)

            類的聲明應當只包含函數(shù)的聲明,永遠不要進行函數(shù)定義(實現(xiàn))。

            基本原理

            類聲明中定義函數(shù)干擾了類對其實現(xiàn)細節(jié)的規(guī)約;使類的接口難以辨認并難以閱讀;并增加了對編譯的依賴。
            類聲明中的函數(shù)定義也減少了對內(nèi)嵌函數(shù)的控制(請參照“使用 No_Inline 條件編譯符禁止內(nèi)嵌編譯”)。

            明確聲明構造函數(shù)的類使用默認構造函數(shù)

            為了在數(shù)組中或任何 STL 容器中使用類,類必須提供公共的默認構造函數(shù)或允許編譯器生成一個構造函數(shù)。

            注意:

            當類有非靜態(tài)引用類型的數(shù)據(jù)成員時是以上規(guī)則的一個例外,這種情況下通常不可能創(chuàng)建一個有意義的默認構造函數(shù)。因此,使用對象數(shù)據(jù)成員的引用就是不可靠的。

            帶有指針類型數(shù)據(jù)成員的類要聲明其復制構造函數(shù)和賦值操作符

            如果需要,并且沒有明確聲明時,編譯器會為類暗中生成一個復制構造函數(shù)和一個賦值操作符。編譯器定義了復制構造函數(shù)和賦值操作符,在 Smalltalk 術語中這通常稱為“shallow-copy”(淺拷貝):明確的說,即按成員拷貝以及對指針按位拷貝。使用編譯器生成的復制構造函數(shù)和默認的賦值操作符肯定會造成內(nèi)存泄漏。

            示例
            //摘自 [Meyers, 92].
            void f()
            {
            String hello("Hello");//假設 String 類型
                                  //由指向 char 型
                                  //數(shù)組的指針來實現(xiàn)。
            { //進入新的域(程序塊)
            String world("World");
            world = hello;        //賦值語句使 world 丟失了
                                  //它最初指向的內(nèi)存單元
            }	//從域中退出時
            	//解構 world;
            	//此時也間接的解構了 hello
            String hello2 = hello; //將已解構過的 hello 賦給
                                   // hello2
            }      

            以上代碼中,儲存字符串“World”的內(nèi)存單元在賦值語句之后丟失了。結(jié)束內(nèi)部程序塊時,銷毀了world;因此,由 hello 引用的內(nèi)存單元也丟失了。已經(jīng)解構的 hello 賦給了 hello2

            示例
            //摘自 [Meyers, 1992]。
            void foo(String bar) {};
            void f()
            {
            String lost = "String that will be lost!";
            foo(lost);
            }      

            以上代碼中,當調(diào)用函數(shù) foo 使用實參 lost 時,利用編譯器定義的復制構造函數(shù)將 lost 復制到 foo 中。因為復制 lost 時對指向“String that will be lost!”的指針進行逐位復制,當從 foo 中退出時,對 lost 的復制(即形參 bar,譯者注)將會被銷毀(假設析構函數(shù)正確的釋放了內(nèi)存單元),同時存儲字符串“String that will be lost! ”的內(nèi)存單元也會被釋放。

            不要重復聲明構造函數(shù)參數(shù)有默認值

            示例
            //示例摘自 [X3J16, 95; section 12.8]
            class X {
            public:
            X(const X&, int);	// int 參數(shù)沒有
            				   //初始化
            				   //沒有用戶聲明的復制構造函數(shù),因此
            				   //編譯器暗中聲明了一個。
            };
            //int 參數(shù)滯后的初始化
            //將構造函數(shù)變成了復制構造函數(shù)。
            //
            X::X(const X& x, int i = 0) { ...}      
            基本原理

            編譯器在類聲明中沒有發(fā)現(xiàn)“標準”的復制構造函數(shù)時會暗中聲明一個復制構造函數(shù)。但是,默認參數(shù)滯后的初始化可能將構造函數(shù)改變成復制構造函數(shù):使用復制構造函數(shù)時導致混淆。因為這種混淆,任何復制構造函數(shù)的使用都因此而變?yōu)椴B(tài)的。[X3J16, 95; section 12.8].

            將析構函數(shù)總是聲明為 virtual 類型

            除非明確將類的設計為不可繼承的,否則應將析構函數(shù)聲明為 virtual 類型。

            基本原理

            如果基類的析構函數(shù)沒有聲明為 virtual 類型,則通過基類的指針或引用刪除派生類對象將導致不確定的行為。

            示例
            //為了簡便起見,這里使用了不好的命名風格
            class B {
            public:
            B(size_t size) { tp = new T[size]; }
            ~B() { delete [] tp; tp = 0; }
            //...
            private:
            T* tp;
            };
            
            class D : public B {
            public:
            D(size_t size) : B(size) {}
            ~D() {}
            //... 
            };
            
            void f()
            {
            B* bp = new D(10);
            delete bp; //由于基類
            		    //的析構函數(shù)沒有定義成 virtual 類型,
            	       //這里的行為是不確定的
            }      

            避免聲明太多的轉(zhuǎn)換操作符和單參數(shù)構造函數(shù)

            使用限定符 explicit 可防止單參數(shù)構造函數(shù)用于隱式轉(zhuǎn)換。

            不要重定義非虛函數(shù)

            非虛函數(shù)實現(xiàn)固定的行為,并且不希望專用于派生類。違反這一原則將導致不可預料的行為:同一對象不同時間可能表現(xiàn)不同的行為。
            非虛函數(shù)是靜態(tài)綁定的;以下示例中,對象函數(shù)的調(diào)用由變量(指向 A 或 B 的指針)的靜態(tài)類型控制,而不是對象的實際類型。

            示例
            // Adapted from [Meyers, 92].
            class A {
            public:
            oid f(); //非虛函數(shù):靜態(tài)綁定
            };
            class B : public A {
            public:
            void f(); //非虛函數(shù):靜態(tài)綁定
            };
            void g()
            {
            B x;
            A* pA = &x; //靜態(tài)類型,指向 A 的指針
            B* pB = &x; //靜態(tài)類型,指向 B 的指針
            pA->f(); //調(diào)用 A::f
            pB->f(); //調(diào)用 B::f
            }      

            謹慎使用非虛函數(shù)

            因為非虛函數(shù)通過限制特殊化(即重載,譯者注)和多態(tài)的使用來限制子類,聲明為非虛擬之前,必須注意確保操作對所有子類確實是固定不變的。

            使用初始化構造函數(shù)而不是使用構造函數(shù)中的賦值語句

            對象構造時其狀態(tài)的初始化應由初始化構造函數(shù)(一個成員初始化列表)完成,而不是通過構造函數(shù)內(nèi)的賦值操作符完成。

            示例
            代碼如下:
            class X 
            {
            public:
            X();
            private
            Y the_y;
            };
            X::X() : the_y(some_y_expression) { } 
            //
            // “the_y”由初始化構造函數(shù)進行初始化
            而不是如下進行初始化
            X::X() { the_y = some_y_expression; }
            //
            // “the_y”由賦值操作符進行初始化
            基本原理

            對象的構造涉及到在執(zhí)行構造函數(shù)體之前構造所有的基類以及數(shù)據(jù)成員。使用初始化構造函數(shù)只需要一個操作(由初值構造),而在構造函數(shù)體中初始化數(shù)據(jù)成員需要兩個操作(構造以及賦值)。
            對有多重嵌套的類(類包含類,所包含的類又包含其他的類),多個操作(構造+成員賦值)造成的性能的開支就十分重要了。

            初始化構造函數(shù)不要調(diào)用成員函數(shù)

            示例
            class A 
            {
            public:
            A(int an_int);
            };
            class B : public A
            {
            public:
            int f();
            B();
            };
            
            B::B() : A(f()) {} 
            //不確定:調(diào)用成員函數(shù)時A
            //尚未初始化[X3J16, 95].
            基本原理

            當所有基類的成員初始化完成之前如果初始化構造函數(shù)直接或間接的調(diào)用了成員函數(shù),此操作的結(jié)果是不確定的。[X3J16, 95].

            注意構造函數(shù)和析構函數(shù)調(diào)用時的情況

            構造函數(shù)中調(diào)用成員函數(shù)時要格外注意;即使調(diào)用的是虛函數(shù)時也要注意。執(zhí)行的將是在類或其基類的構造函數(shù)、析構函數(shù)中定義的操作。

            整形類常量使用 static const

            定義整形(整數(shù))類常量時,使用 static const 數(shù)據(jù)成員,不要使用 #define 或全局常量。如果編譯器不支持 static const,則使用 enum

            示例
            代碼如下:
            class X {
            static const buffer_size = 100;
            char buffer[buffer_size];
            };
            static const buffer_size;
            或:
            class C {
            enum { buffer_size = 100 };
            char buffer[buffer_size];
            };
            但不要使用:
            #define BUFFER_SIZE 100
            class C {
            char buffer[BUFFER_SIZE];
            };      

            函數(shù)

            一定要明確聲明函數(shù)的返回值類型

            這可以防止編譯器發(fā)現(xiàn)函數(shù)未聲明返回值類型時所造成的模糊不清。

            函數(shù)聲明中要提供正規(guī)參數(shù)名稱

            函數(shù)的聲明和定義中要使用相同的名稱;這可使混淆程度降至最小。提供參數(shù)名稱有利于提高代碼的易注釋性和易讀性。

            函數(shù)要盡量只有一個返回點

            返回語句在程序體中自由放置與 goto 語句的情況類似,會造成代碼難以閱讀和維護。
            只有在少數(shù)幾種函數(shù)中才能容忍出現(xiàn)多個返回語句,此時可以同時看到所有的返回語句 return 并且代碼還有非常規(guī)則的結(jié)構:

            type_t foo()
            {
            
            if (this_condition)
            return this_value;
            else
            return some_other_value;
            }      

            函數(shù)的返回類型為 void 時無返回語句。

            避免創(chuàng)建對全局有副作用的函數(shù)

            應盡量避免創(chuàng)建對全局有副作用的函數(shù),因為它可能改變并不知情的數(shù)據(jù)而非它們內(nèi)部對象的狀態(tài),如全局和名字空間數(shù)據(jù)(另請參見“盡量不使用全局或名字空間范圍的數(shù)據(jù)”)但如果這是不可避免的,則應明確記錄下所有的副作用以作為函數(shù)規(guī)約的一部分。
            只在所需的對象中傳遞參數(shù)增加了代碼的上下文無關性,并提高了它的強壯性和易理解性。

            以重要性和活躍性遞減的順序聲明函數(shù)的參數(shù)

            從調(diào)用者的角度講,參數(shù)聲明的順序是非常重要的:

            • 首先以重要性遞減的順序定義非默認參數(shù);
            • 再以被修改可能性遞減的順序定義有默認值的參數(shù)。

            這種順序使用了參數(shù)默認值的優(yōu)點來減少函數(shù)調(diào)用中實參的個數(shù)。

            避免聲明帶有不定參數(shù)個數(shù)的函數(shù)

            帶有不定參數(shù)個數(shù)的函數(shù)無法對其實參進行類型檢查。

            避免重復聲明帶有默認參數(shù)的函數(shù)

            函數(shù)的進一步重復聲明中應避免添加默認值:遠離先前聲明,一個函數(shù)只應聲明一次。否則,如果讀者沒有意識到此后的聲明,這將會造成混淆。

            函數(shù)聲明中盡可能使用 const

            檢查函數(shù)是否有常量行為(返回常數(shù)值;接受常量實參;或其操作不帶有副作用),如果有則使用限定符 const 表明其行為。

            示例
            const T f(...); //函數(shù)返回一個常量
            		         //對象。
            T f(T* const arg);	        //函數(shù)接受一個常量指針
            			          //為其參數(shù)。
            //可以改變指針所指的對象,
            //但不可以改變指針本身。
            T f(const T* arg);      //函數(shù)參數(shù)為指針類型
            T f(const T& arg);      // 函數(shù)參數(shù)為引用類型
                                    //它們都指向一個常量對象。指針可以
                                    //改變,但所指的對象
                                    //不能改變。
            T f(const T* const arg);  //函數(shù)參數(shù)為
                                      //常量指針,它指向常量對象。
                                      //指針和它所指的對象
                                      //都不改變。
            T f(...) const;  //函數(shù)沒有副作用:
                             //不改變它對象的狀態(tài)
                             //所以可應用于
                             //常量對象。

            避免利用值傳遞對象

            利用值傳遞和返回對象可能帶來構造函數(shù)和析構函數(shù)的大量開支。可通過引用傳遞和返回對象,這樣可避免構造函數(shù)和析構函數(shù)的開支。
            可用 Const 引用指定通過引用傳遞的實參是不可改變的。復制構造函數(shù)和賦值操作符是典型的示例:

            C::C(const C& aC);
            C& C::operator=(const C& aC);
            示例:

            考慮以下例子:

            the_class the_class::return_by_value(the_class a_copy)
            {
            return a_copy;
            }
            the_class an_object;
            return_by_value(an_object);

            調(diào)用函數(shù) return_by_value,其實參為 an_object,此時調(diào)用 the_class 的復制構造函數(shù)將 an_object 復制到 a_copy。再次調(diào)用 the_class 的復制構造函數(shù)將 a_copy 復制到函數(shù)返回的臨時對象中。從函數(shù)返回時調(diào)用 the_class 的析構函數(shù)銷毀 a_copy。一段時間之后再次調(diào)用 the_class 的析構函數(shù)銷毀 return_by_value 返回的對象。以上不完成任何事的函數(shù)的所有開支是兩個構造函數(shù)和兩個析構函數(shù)。
            如果 the_class 是個派生類且包含其他類的成員數(shù)據(jù),情況會更糟:會調(diào)用基類以及所包含類的構造函數(shù)和析構函數(shù),因此函數(shù)調(diào)用所帶來的構造函數(shù)和析構函數(shù)的調(diào)用就會隨之急劇增長。

            注意:

            以上指南似乎旨在建議開發(fā)人員在傳遞和返回對象時總是使用引用類型,但應格外當心不要返回引用到局部對象上,當需要對象時,也不要返回引用。返回引用到局部對象會造成災難性后果,因為函數(shù)返回時,所返回的引用綁定到了所銷毀的對象上。

            不能返回引用到局部對象

            離開函數(shù)作用域時會銷毀局部對象;使用銷毀了的對象會造成災難。

            不可返回由 new 初始化,之后又已解除引用的指針

            違背這一原則將導致內(nèi)存泄漏

            示例
            class C {
            public:
            ...
            friend C& operator+( const C& left,
            const C& right);
            };
            C& operator+(const C& left, const C& right)
            {
            C* new_c = new C(left..., right...);
            return *new_c;
            }
            C a, b, c, d;
            C sum;
            sum = a + b + c + d;

            因為在計算加和 sum 值時沒有存儲操作符“+”的中間結(jié)果,因此中間對象不能刪除,否則會導致內(nèi)存泄漏。

            不要返回非常量引用或指針到成員數(shù)據(jù)上

            違背了這一原則也就違背了數(shù)據(jù)的封裝性,可能導致不好的結(jié)果。

            宏擴展使用內(nèi)嵌定義函數(shù)不使用 #define

            但要有選擇的使用內(nèi)嵌定義:只對非常小的函數(shù)才使用;內(nèi)嵌定義大型函數(shù)可能導致代碼膨脹。
            由于用戶代碼編譯時需要內(nèi)嵌定義函數(shù)的實現(xiàn),因此內(nèi)嵌定義函數(shù)也增加了模塊間編譯的依賴。
            [Meyers, 1992] 提供了以下不良宏使用極端示例的詳細討論:

            示例

            不要這樣做:

            #define MAX(a, b) ((a) > (b) ?(a) : (b))

            而應這樣做:

            inline int max(int a, int b) { return a > b ? a : b; }

            MAX 有好幾個問題:它不是安全的類型;它的行為不是確定的:

            int a = 1, b = 0;
            MAX(a++, b);     // a 增 1 了兩次
            MAX(a++, b+10);  // a 增 1 了一次
            MAX(a, "Hello"); //比較整形和指針

            使用默認參數(shù)而不使用函數(shù)重載

            當只使用一個算法并且此算法可由少量幾個參數(shù)進行參數(shù)化時,使用默認參數(shù)而不使用函數(shù)重載。
            使用默認參數(shù)有利于減少重載函數(shù)的數(shù)量、提高可維護性、減少函數(shù)調(diào)用時所需的實參數(shù)量、以及提高代碼的可讀性。

            使用函數(shù)重載表達常用語義

            當同一語義操作需要多個實現(xiàn)時使用函數(shù)重載,但重載使用不同的實參類型。
            重載操作符時保留其傳統(tǒng)含義。不要忘記定義關系操作符,如操作符 operator==operator!=

            避免重載以指針和整形數(shù)為實參的函數(shù)

            避免使用帶單一整形實參的函數(shù)來重載帶單一指針實參的函數(shù):

            void f(char* p);
            void f(int i);

            以下調(diào)用可能會導致混淆:

            PRE>f(NULL); f(0);

            此重載解析為 f(int) 而不是 f(char*)

            令操作符 operator= 返回對 *this 的引用

            C++ 允許賦值鏈:

            String x, y, z;
            x = y = z = "A string";

            因為復制操作符是右結(jié)合的,將字符串“A string”賦給 z,再將 z 賦給 y,最后將 y 賦給 x。以從右到左的順序,對每個在 = 右端的表達式有效的調(diào)用一次 operator= 。這也意味著每一次 operator= 運算的結(jié)果都是一個對象,但其左端或右端的對象都有可能成為返回值。
            使用復制操作符好的做法總有以下形式:

            C& C::operator=(const C&);

            只有左端的對象是可能的(rhs 為常量引用,lhs 為變量引用),因此應返回 *this。詳情請參見[Meyers, 1992]

            使 operator= 檢查自賦值

            執(zhí)行檢查有兩點重要的理由:首先,派生類對象的賦值涉及到調(diào)用每一個基類(在繼承層次結(jié)構中位于此類的上方)的賦值操作符,跳過這些操作符就可以節(jié)省很多運行時間。其次,在復制“rvalue”對象前,賦值涉及到解構“l(fā)value”對象。在自賦值時,rvalue 對象在賦值前就已銷毀了,因此賦值的結(jié)果是不確定的。

            盡可能減少復雜性

            不要書寫過長的函數(shù),例如其代碼超過 60 行。
            盡可能減少返回語句的數(shù)量,1 是理想值。
            盡量使循環(huán)復雜性降到 10 以下(對單一退出語句函數(shù)為判定語句總數(shù)加 1)。
            盡量使擴展循環(huán)復雜性降到 15 以下(對單一退出語句函數(shù)為判定語句+邏輯操作符+1的和)。
            盡量減小引用的平均最大作用范圍(局部對象聲明與對象第一次使用間的行距)。

            類型

            定義項目范圍的全局系統(tǒng)類型

            大型項目中通常有整個系統(tǒng)中經(jīng)常使用的類型的集合;這種情況下,將這些類型收集入一個或多個低級全局實用程序的名字空間中,這樣做是明智的(請參見“避免使用基本類型”的示例)。

            避免使用基本類型

            當對可移植性要求較高、需要控制數(shù)字對象占據(jù)的內(nèi)存空間或需要一個具體范圍的值時,不使用基本類型。這種情況下最好通過使用適當?shù)幕绢愋兔鞔_聲明帶有大小限制的類型的名稱。
            確保基本類型不會通過循環(huán)計數(shù)器、數(shù)組下標等潛回代碼中。

            示例
            namespace system_types {
            typedef unsigned char byte;
            typedef short int integer16; //16 位有符號整數(shù)
            typedef int integer32; //32 位有符號整數(shù)
            typedef unsigned short int natural16; //16 位無符號整數(shù)
            typedef unsigned int natural32; //32 位無符號整數(shù)
            ...
            }      
            基本原理

            基本類型的表示依賴于具體實施。

            使用 typedef 創(chuàng)建同義詞來加強局部含義

            對現(xiàn)有名稱使用 typedef 創(chuàng)建同義詞,提供更有意義的局部名稱并提高易讀性(這樣做不會增加運行時間)。
            typedef 也可用來提供受限名稱的速記法。

            示例
            //標準庫的向量聲明
            //
            namespace std {
            template <class T, class Alloc = allocator>
            class vector {
            public:
            typedef typename 
            Alloc::types<T>reference reference;
            typedef typename 
            Alloc::types<T>const_reference const_reference;
            typedef typename 
            Alloc::types<T>pointer iterator;
            typedef typename 
            Alloc::types<T>const_pointer const_iterator;
            ...
            }
            }      

            使用 typedef 創(chuàng)建的名稱時,同一代碼段中不要混合使用原名稱和它的同義詞。

            常量與對象

            避免使用常量值

            使用引用形式的已命名常量

            定義常量時避免使用 #define 預處理指示符

            代之以 constenum
            不要這樣做:

            #define LIGHT_SPEED 3E8
            而應這樣做:
            const int light_speed = 3E8;
            或調(diào)整數(shù)組大小如下:
            enum { small_buffer_size = 100, 
            large_buffer_size = 1000 };
            基本原理

            因為由 #defines 引入的名稱在編譯預處理時會被替換掉,不出現(xiàn)在符號表中,因此調(diào)試就更加困難了。

            對象聲明靠近其第一次使用點
            聲明時總要初始化 const 對象

            未聲明為 externconst 對象有內(nèi)部鏈接,聲明時初始化這些常量對象允許在編譯時使用初始化函數(shù)。

            永遠不要忘記常量對象的“不變性”

            常量對象可能存在于只讀存儲器中。

            定義時初始化對象

            如果對象不是自初始化的,在對象定義中指定初始值。如果不可能指定一個有意義的初始值,則賦值“nil”或考慮后面再作定義。
            對大型對象,通常不建議先構造對象,此后再使用賦值語句初始化的作法。因為這樣做的代價高昂(請參見“使用初始化構造函數(shù)而不使用構造函數(shù)中的賦值語句”)。
            如果構造時不可能合適的初始化對象,則使用傳統(tǒng)的“nil”值進行對象初始化,它意味著“未初始化”。只在初始化過程中聲明“不可用但值已知”的對象時才使用 nil 值。但算法在受控模式下可以拒絕接受:在對象合適的初始化之前使用它時可以指出這是未經(jīng)初始化的變量錯誤。
            注意有時不可能所有類型都聲明為 nil 值,尤其是在模運算類型中,例如角度。I這種情況下選擇最不可能出現(xiàn)的值。


            第六章

            表達式和語句

            本章對不同種類的 C++ 表達式和語句提供指南。

            表達式

            使用冗余的圓括號使復合表達式含義更加清晰
            避免表達式的過深嵌套

            表達式的嵌套層數(shù)定義為:忽略操作符優(yōu)先級原則時,從左到右求表達式的值所需圓括號對的嵌套數(shù)。
            嵌套層數(shù)過多會使表達式難以理解。

            不要假定任何特殊的表達式計算次序

            除非計算次序由操作符指定(如逗號操作符、三元表達式、以及連接和分離),否則不要假定任何特殊的計算次序,因為這種假定可能導致嚴重的混淆并降低可移植性。
            例如,同一語句中不要混合使用變量的增 1 和減 1 操作。

            示例
            foo(i, i++);
            array[i] = i--;

            空指針使用 0 而不使用 NULL

            空指針應使用 0 還是 NULL 是一個有高度爭議的論題。
            C 和 C++ 中定義任何零值常量表達式都可解釋為空指針。因為零難以閱讀,并且不贊成使用常量,傳統(tǒng)上程序員使用宏 NULL 作為空指針。不幸的是,NULL 沒有可移植的定義。一些 ANSI C 編譯器使用 (void *)0,但對 C++ 來說這不是一個好的選擇:

            char* cp = (void*)0; /* C 合法但 C++ 不合法*/

            因此任何形如 (T*)0 而非一個 0 的 NULL 定義,在C++ 中都需要類型轉(zhuǎn)換。歷史上,很多指南都擁護使用 0 代替空指針以減少類型轉(zhuǎn)換、增強代碼的可移植性。但許多 C++ 開發(fā)人員還是感覺使用 NULL 比 0 更加舒服,也爭辯說當今多數(shù)的編譯器(準確的說是多數(shù)頭文件)將 NULL 實施為 0。
            本指南傾向使用 0,因為不管 NULL 的值是多少,0 都是有效的。但由于爭議,這一點降級為一個小技巧,可在適當?shù)臅r候遵循或忽略它。

            不要使用舊類型轉(zhuǎn)換

            使用新類型轉(zhuǎn)換符(dynamic_cast、static_cast、reinterpret_cast、const_cast)而不要使用舊類型轉(zhuǎn)換符。
            如果您沒有新類型轉(zhuǎn)換符,避免同時轉(zhuǎn)換,特別是向下轉(zhuǎn)換(將基類對象轉(zhuǎn)換成派生類對象)。
            使用轉(zhuǎn)換操作符如下:

            • dynamic_cast - 類同一層次(子類型)成員間的的轉(zhuǎn)換,使用運行類型信息(對帶虛函數(shù)的類,運行類型信息是可用的)。這種類間的轉(zhuǎn)換一定是安全的。
            • static_cast - 類同一層次成員間的的轉(zhuǎn)換,不使用運行類型信息;所以這可能是不安全的。如果程序員不能保證類型的安全性,則使用 dynamic_cast。
            • reinterpret_cast - 不相關指針類型和整形(整數(shù))間的轉(zhuǎn)換;這是不安全的,只應在所提及的類型間使用。
            • const_cast - 將指定為 const 型函數(shù)實參的“不變性”轉(zhuǎn)換掉。注意:const_cast 不打算將確實定義為常量對象(它可能位于只讀存儲器中)的“不變性”轉(zhuǎn)換掉。

            不要使用 typeid 實現(xiàn)類型轉(zhuǎn)換邏輯:使類型轉(zhuǎn)換操作符完成類型檢查和轉(zhuǎn)換的操作不可再分,詳情請參見 [Stroustrup, 1994]

            示例

            不要這樣做:

            void foo (const base& b)
            {
            if (typeid(b) == typeid(derived1)) {
            do_derived1_stuff();
            else if (typeid(b) == typeid(derived2)) {
            do_derived2_stuff();
            else if () {
            
            }
            }      
            基本原理

            舊類型轉(zhuǎn)換使類型系統(tǒng)失效,可能導致編譯器難以察覺的缺陷:內(nèi)存管理系統(tǒng)可能會崩潰,虛函數(shù)表可能遭嚴重錯誤修改,當對象作為派生類對象訪問時無關對象可能會遭破壞。注意即使讀訪問也可能造成破壞,因為有可能引用了并不存在的指針或區(qū)域。
            新類型轉(zhuǎn)換操作符使類型轉(zhuǎn)換更加安全(多數(shù)情況下)、更加明確。

            Boolean(布爾)表達式使用新的 bool 類型

            不要使用舊的 Boolean 宏或常數(shù):沒有標準的布爾值 true(真);代之以新的 bool 類型。

            布爾值 true(真)之間不要直接比較

            因為傳統(tǒng)上 true 值 (1 或 !0) 沒有標準值,非零表達式和真值的比較就可能無效。
            代之以使用布爾表達式。

            示例

            不要這樣做:

            if (someNonZeroExpression == true) 
            //可能不會解釋為真值條件
            最好這樣做:
            if (someNonZeroExpression) 
            //一定會解釋為一個真值條件

            指針不要與不在同一數(shù)組中的對象比較

            這種操作的結(jié)果幾乎都是毫無意義的。

            已刪除的對象指針要賦予空指針值

            設置已刪除對象的指針為空指針可避免災難的發(fā)生:重復刪除非空指針是有害的,但重復刪除空指針是無害的。
            即使在函數(shù)返回前,刪除操作后也總要賦一個空指針,因為以后可能添加新的代碼。

            語句

            當從布爾表達式分支時使用 if 語句
            當從離散值分支時使用 switch 語句

            當分支條件為離散值時使用 switch 語句而不使用一系列的“else if”。

            一定為 switch 語句提供一個 default 分支以記錄錯誤

            switch 語句應總包含一個 default 分支,default 分支應用以捕獲錯誤。
            這一原則保證了當引入新的 switch 值而處理這一新值的分支被刪除時,現(xiàn)有的 default 分支可以捕獲到錯誤。

            當循環(huán)需要迭代前測試時使用 for 語句或 while 語句

            當?shù)脱h(huán)的結(jié)束是根據(jù)循環(huán)計數(shù)器的值時使用 for 語句不使用 while 語句。

            當循環(huán)需要迭代后測試時使用 do while 語句
            循環(huán)中避免使用 jump 語句

            避免使用除循環(huán)結(jié)束條件以外的循環(huán)退出方式(使用 break、returngoto),不要使用 continue 不成熟的跳轉(zhuǎn)到下一迭代上。這減少了控制路徑流程的數(shù)量,使代碼更易理解。

            不要使用 goto 語句

            這是一條通用原則。

            避免嵌套作用域內(nèi)隱藏標識符

            這會使讀者模糊不清,維護時造成潛在的危險。


            第七章

            特殊主題

            本章為內(nèi)存管理與錯誤報告提供指南

            內(nèi)存管理

            C 和 C++ 內(nèi)存操作要避免混合使用

            C 庫函數(shù) malloccallocrealloc 不應用于分配對象空間:此時應使用 C++ 操作符 new。
            只有在內(nèi)存?zhèn)鬟f給C 庫函數(shù)處理時,才使用 C 函數(shù)分配內(nèi)存。
            不要使用 delete 來釋放由 C 函數(shù)分配的內(nèi)存,或釋放由 new 創(chuàng)建的對象。

            刪除由 new 創(chuàng)建的數(shù)組對象時總使用 delete[]

            使用 delete 刪除數(shù)組對象時如果不使用空方括號(“[]”),則只刪除數(shù)組的第一個元素,因此導致內(nèi)存泄漏。

            錯誤處理和異常

            由于使用 C++ 異常機制的經(jīng)驗不足,此處的指南將來可能會作較大的改變。
            C++ 草擬標準定義了兩大類錯誤:邏輯錯誤和運行錯誤。邏輯錯誤是可避免的編程錯誤。運行錯誤定義為程序范圍外事件導致的錯誤。
            使用異常的通用規(guī)則為:正常條件下的系統(tǒng)如果沒有重載或硬件錯誤則不應當產(chǎn)生任何異常。

            開發(fā)過程中多使用聲明語句以發(fā)現(xiàn)錯誤

            開發(fā)過程中使用函數(shù)的前置和后置條件聲明語句以發(fā)現(xiàn)“倒斃”錯誤。
            在實現(xiàn)最終錯誤處理代碼前,聲明語句提供了簡單有用的臨時錯誤監(jiān)測機制。聲明語句還有額外的好處,使用“NDEBUG”預處理符,可在編譯時去掉這些聲明(請參見“用具體值定義 NDEBUG 預處理符”)。
            傳統(tǒng)上使用宏 assert 達到這一目的;但文獻 [Stroustrup, 1994] 提供了一個可替代的模板如下。

            示例
            template<class T, class Exception>
            inline void assert ( T a_boolean_expression, 
            Exception the_exception)
            {
            if (! NDEBUG)
            if (! a_boolean_expression) 
            throw the_exception;
            }      

            只在真實的異常情況時使用異常

            對經(jīng)常的、預料中的事件不要使用異常:異常打斷了代碼的正常控制流程,使它難以理解和維護。
            預料中的事件應用代碼的正常控制流程處理;需要時使用函數(shù)返回值或“out”參數(shù)狀態(tài)碼。
            異常也不應用于實現(xiàn)控制結(jié)構:這會是另一種形式的“goto”語句。

            從標準異常中派生項目異常

            這保證了所有異常都支持常用操作的最小集合并可由一小組高級處理程序處理。
            使用邏輯錯誤(域錯誤、非法實參錯誤、長度錯誤和越界錯誤)表示應用程序域錯誤、函數(shù)調(diào)用傳遞了非法實參、對象構造超出了它們允許的大小以及實參值不在允許范圍內(nèi)。
            使用運行錯誤(范圍錯誤和溢出錯誤)表示只有在運行時才能查出的算術和配置錯誤、數(shù)據(jù)已遭破壞或資源耗盡錯誤。

            給定抽象盡量少使用異常

            大型系統(tǒng)中,每一層都不得不處理大量的異常會使代碼難以閱讀和維護。異常處理可能會嚴重影響了正常處理。
            減少使用異常的方法有:
            通過使用少量的異常種類在抽象間共享異常。
            引發(fā)派生于標準異常的特殊異常,處理更通用的異常。
            為對象添加“異常”狀態(tài),提供明確檢查對象合法性的原語。

            將所有異常聲明為已引發(fā)

            產(chǎn)生異常(而不僅僅是傳遞異常)的函數(shù)應在它們的異常規(guī)約中將所有異常聲明為已引發(fā):它們不應平靜的產(chǎn)生異常而不警告它們的用戶。

            在異常首次出現(xiàn)時就報告它

            開發(fā)過程中,采用合適的記錄機制盡早報告異常,包括在“異常引發(fā)點”。

            按照從派生結(jié)構最底端到最頂端的順序定義異常處理

            定義異常處理要按照從派生結(jié)構最底端到最頂端(即從最終子類到最初父類)的順序,這樣可以避免編寫無法到達的處理過程;請參見下面的示例 how-not-to-it。因為是以聲明的順序匹配處理過程的,所以這也保證了異常由最合適的處理過程捕獲。

            示例

            不要這樣做:

            class base { ...};
            class derived : public base { ...};
            ...
            try {
            ...
            throw derived(...);
            //
            //引發(fā)一個派生類異常
            }
            catch (base& a_base_failure)
            //
            //但基類異常處理過程“捕獲”了它,因為
            //基類處理過程首先匹配!
            {
            ...
            }
            catch (derived& a_derived_failure) 
            //
            //這一處理過程是無法達到的!
            { 
            ...
            }      

            避免使用捕獲所有異常的處理過程

            避免使用捕獲所有異常的處理過程(處理過程聲明使用 “…”),除非重引發(fā)了異常只有在做本地內(nèi)務管理時才使用捕獲所有異常的處理過程,這時應重引發(fā)異常以避免掩蓋了本層不能處理此異常的事實。

            try {
            ...
            }
            catch (...)
            { 
            if (io.is_open(local_file))
            {
            io.close(local_file);
            }
            throw;
            }      

            確保函數(shù)狀態(tài)代碼有合適的值

            當狀態(tài)代碼作為函數(shù)參數(shù)返回時,要賦值給參數(shù)作為程序體中的第一個可執(zhí)行語句。系統(tǒng)的設置所有狀態(tài)的默認值為成功或失敗。考慮函數(shù)所有可能的出口,包括異常處理。

            本地執(zhí)行安全檢查;不要希望您的客戶會這樣做

            如果沒有合適的輸入,函數(shù)就會產(chǎn)生錯誤輸出,在函數(shù)中裝載代碼以受控方式監(jiān)測和報告非法輸入。不要依賴于標注來告知客戶傳遞合適的值。事實上標注遲早會被忽略掉,如果檢測到非法參數(shù)就會導致難以調(diào)試的錯誤。


            第八章

            可移植性

            本章論述先驗不可移植的語言特征。

            路徑名

            不要使用絕對代碼文件路徑名

            各操作系統(tǒng)中的路徑名不是以標準形式表示的。使用它們將會引入對平臺的依賴。

            示例
            #include "somePath/filename.hh" // Unix
            #include "somePath\filename.hh" // MSDOS

            數(shù)據(jù)表示

            類型的表示和排列高度依賴于機器的結(jié)構。對表示和排列的假設會導致混淆,降低可移植性。

            不要假設類型的表示

            特別的,不可以在 int、長整形或任何其他數(shù)字類型中存儲指針類型,因為這是高度不可移植的。

            不要假設類型的排列

            不要依賴于一個特殊的下溢或上溢行為
            盡可能使用“可伸縮”常量

            可伸縮常量避免了字長變化的問題。

            示例
            const int all_ones = ~0;
            const int last_3_bits = ~0x7;

            類型轉(zhuǎn)換

            不要從一個“短”類型轉(zhuǎn)換成一個“長類型”

            機器結(jié)構可能指定了某種類型的排列方式。從需要較松散排列的類型轉(zhuǎn)換為需要較嚴密排列的類型可能會導致程序錯誤。


            第九章

            復用

            本章為 C++ 代碼復用提供指南。

            盡可能使用標準庫構件

            如果沒有標準庫,則基于標準庫接口創(chuàng)建類,這有利于將來的移植。

            使用模板復用獨立于數(shù)據(jù)的行為

            當行為不依賴于一個具體的數(shù)據(jù)類型時,使用模板復用行為。

            使用公共繼承復用類接口(子類型化)

            使用 public 繼承表達“is a”關系并復用基類接口,還可有選擇的復用它們的實現(xiàn)。

            使用包容而不是私有繼承復用類的實現(xiàn)

            當復用實現(xiàn)或建模“部分/整體”關系時,避免使用私有繼承。復用未重新定義的實現(xiàn)最好由包容而非私有繼承來實現(xiàn)。
            當需要重新定義基類的操作時使用私有繼承。

            謹慎使用多繼承

            多繼承應謹慎使用,因為它帶來了許多額外的復雜性。[Meyers, 1992] 提供了對潛在名稱歧義和重復繼承所帶來復雜性的詳細討論。復雜性來自于:
            歧義,當多個類使用相同的名稱時,任何對名稱不加限定的引用天生就是產(chǎn)生歧義的。可通過使用類名稱限定其成員名稱來解決歧義的問題。但這也帶來了不幸的后果:使多態(tài)無效且將虛函數(shù)準變成了靜態(tài)綁定函數(shù)。
            從同一基類重復繼承(派生類通過繼承結(jié)構的不同路徑多次繼承一個基類)多組數(shù)據(jù)成員產(chǎn)生了如下問題:應當使用多組數(shù)據(jù)成員中的哪一個?
            可使用虛繼承(對虛基類的繼承)防止多繼承數(shù)據(jù)成員。那么為什么不總使用虛繼承呢?虛繼承有負面影響,會改變基礎對象表示并降低訪問的效率。
            要求所有的繼承都是虛擬的,同時也就強加了包羅一切的空間,帶來了時間上的低效,實現(xiàn)這樣的政策過于獨斷了。
            因此,為了決定是使用虛繼承還是非虛繼承,多繼承需要類的設計者對將來類的使用有敏銳的洞察力。


            第十章

            編譯問題

            本章為編譯問題指南

            盡量減少對編譯的依賴

            模塊規(guī)約中不要包含只是此模塊實施所需的其他頭文件。
            避免在只需要指針或引用可見時就為了能看到其他類而在規(guī)約中包含頭文件;代之以預先聲明。

            示例
            //模塊 A 規(guī)約,包含于文件“A.hh”中
            #include "B.hh" //當只有實施需要時
                            //不要包含。
            #include "C.hh" //只有引用需要時不要包含;
                            //代之以預先聲明。
            class C;
            class A
            {
            C* a_c_by_reference; //有 a 的引用。
            };
            // “A.hh”結(jié)束
            注意:

            盡量減少對編譯的依賴是某些設計代碼模式或模式的基本原理,如不同的命名:Handle(句柄)或 Envelope(信包)[Meyers, 1992],或 Bridge(橋)[Gamma] 類。將類抽象的責任在兩個關聯(lián)類間分割,一個提供類接口,另一個提供類實現(xiàn);因為任何實現(xiàn)(實現(xiàn)類)上的改變都不再需要客戶重新編譯,因此類與其客戶間的依賴關系就最小化了。

            示例
            //模塊 A 規(guī)約,包含于文件“A.hh”中
            class A_implementation;
            class A
            {
            A_implementation* the_implementation;
            };
            //“A.hh”結(jié)束

            這一做法也允許接口類和實現(xiàn)類特殊化為兩個單獨的類層次結(jié)構。

            用具體值定義 NDEBUG

            通常使用符號 NDEBUG 在編譯過程中去掉由宏 assert 實現(xiàn)的聲明代碼。傳統(tǒng)做法是當需要消除聲明語句時定義這一符號;但是,開發(fā)人員經(jīng)常沒有意識到聲明的存在,因此沒有定義符號。
            我們支持使用聲明的模板版本;這種情況下符號 NDEBUG 必須明確的賦值:需要聲明代碼時為 0;要消除時為非 0。任何沒有提供符號 NDEBUG 以具體值的聲明代碼最終編譯時將產(chǎn)生編譯錯誤;因此,提醒了開發(fā)人員注意聲明代碼的存在。


            指南總結(jié)

            以下是本手冊所有指南的總結(jié)。

            要求或限制

            使用常識
            通常使用 #include 來訪問模塊的規(guī)約
            永遠不要聲明以一個或多個下劃線 ('_') 開頭的名稱
            將全局聲明限定在名字空間中
            明確聲明構造函數(shù)的類使用默認構造函數(shù)
            帶有指針類型數(shù)據(jù)成員的類要聲明其復制構造函數(shù)和賦值操作符
            不要重復聲明構造函數(shù)參數(shù)有默認值
            將析構函數(shù)總是聲明為 virtual 類型
            不要重定義非虛函數(shù)
            初始化構造函數(shù)不要調(diào)用成員函數(shù)
            不能返回引用到局部對象
            不可返回由 new 初始化,之后又已解除引用的指針
            不要返回非常量引用或指針到成員數(shù)據(jù)上
            令操作符 operator= 返回對 *this 的引用
            使 operator= 檢查自賦值
            永遠不要忘記常量對象的“不變性”
            不要假定任何特殊的表達式計算次序
            不要使用舊類型轉(zhuǎn)換
            Boolean(布爾)表達式使用新的 bool 類型
            布爾值 true(真)之間不要直接比較
            指針不要與不在同一數(shù)組中的對象比較
            已刪除的對象指針要賦予空指針值
            一定為 switch 語句提供一個 default 分支以記錄錯誤
            不要使用 goto 語句
            C 和 C++ 內(nèi)存操作要避免混合使用
            刪除由 new 創(chuàng)建的數(shù)組對象時總使用 delete[]
            不要使用絕對代碼文件路徑名
            不要假設類型的表示
            不要假設類型的排列
            不要依賴于一個特殊的下溢或上溢行為
            不要從一個“短”類型轉(zhuǎn)換成一個“長類型”
            用具體值定義 NDEBUG

            建議

            不同文件中放置模塊規(guī)約與實現(xiàn)
            只選擇一組文件擴展名以區(qū)分頭文件和實施文件
            每個模塊規(guī)約避免定義多個類
            避免將私有實施聲明置于模塊的規(guī)約中
            將模塊內(nèi)嵌函數(shù)的定義置于單獨的文件中
            如果程序規(guī)模是個要求考慮的問題,就把大的模塊分割成多個變換單元
            分離平臺依賴性
            防止重復文件包含的保護

            對嵌套語句使用小的、一致的縮進風格
            相對于函數(shù)名或作用域名縮進函數(shù)參數(shù)
            使用能夠適合標準打印紙大小的最大行寬
            使用一致?lián)Q行
            使用 C++ 風格的注釋而非 C 風格的注釋
            注釋與源代碼盡可能靠攏
            避免行末注釋
            避免注釋頭
            使用空注釋行分離注釋段
            避免冗余
            編寫自記錄代碼而非注釋
            記錄類與函數(shù)
            選擇一個名稱約定,使用它要前后一致
            避免使用只靠字母大小寫才能區(qū)分的名稱
            避免使用縮寫
            避免使用后綴來表明程序語言結(jié)構
            選擇清晰的、易辨認的、有意義的名稱
            使用名稱的正確拼寫
            布爾值使用正值謂詞從句
            使用名字空間由子系統(tǒng)或庫劃分潛在全局名稱
            類的名稱使用名詞或名詞短語
            過程類型的函數(shù)名稱使用動詞
            當需要使用同一通用含義時,使用函數(shù)的重載
            實參名使用語法元素強調(diào)其含義
            異常名選用否定的含義
            異常名使用項目定義過的形容詞
            浮點指數(shù)和十六進制數(shù)使用大寫字母
            使用名字空間劃分非類功能
            盡量不使用全局和名字空間范圍的數(shù)據(jù)
            使用 class 而不是 struct 來實現(xiàn)抽象數(shù)據(jù)類型
            以可訪問權限逐次降低的順序聲明類的成員
            抽象數(shù)據(jù)類型避免聲明公共或保護數(shù)據(jù)成員
            使用友元保留封裝性
            避免在類聲明中定義函數(shù)
            避免聲明太多的轉(zhuǎn)換操作符和單參數(shù)構造函數(shù)
            謹慎使用非虛函數(shù)
            使用初始化構造函數(shù)而不是使用構造函數(shù)中的賦值語句
            注意構造函數(shù)和析構函數(shù)調(diào)用時的情況
            整形類常量使用 static const
            一定要明確聲明函數(shù)的返回值類型
            函數(shù)聲明中要提供正規(guī)參數(shù)名稱
            函數(shù)要盡量只有一個返回點
            避免創(chuàng)建對全局有副作用的函數(shù)
            以重要性和活躍性遞減的順序聲明函數(shù)的參數(shù)
            避免聲明帶有不定參數(shù)個數(shù)的函數(shù)
            避免重復聲明帶有默認參數(shù)的函數(shù)
            函數(shù)聲明中盡可能使用 const
            避免利用值傳遞對象
            宏擴展使用內(nèi)嵌定義函數(shù)不使用 #define
            使用默認參數(shù)而不使用函數(shù)重載
            使用函數(shù)重載表達常用語義
            避免重載以指針和整形數(shù)為實參的函數(shù)
            盡可能減少復雜性
            避免使用基本類型
            避免使用常量值
            定義常量時避免使用 #define 預處理指示符
            對象聲明靠近其第一次使用點
            聲明時總要初始化 const 對象
            定義時初始化對象
            當從布爾表達式分支時使用 if 語句
            當從離散值分支時使用 switch 語句
            當循環(huán)需要迭代前測試時使用 for 語句或 while 語句
            當循環(huán)需要迭代后測試時使用 do while 語句
            循環(huán)中避免使用 jump 語句
            避免嵌套作用域內(nèi)隱藏標識符
            開發(fā)過程中多使用聲明語句以發(fā)現(xiàn)錯誤
            只在真實的異常情況時使用異常
            從標準異常中派生項目異常
            給定抽象盡量少使用異常
            將所有異常聲明為已引發(fā)
            按照從派生結(jié)構最底端到最頂端的順序定義異常處理
            避免使用捕獲所有異常的處理過程
            確保函數(shù)狀態(tài)代碼有合適的值
            本地執(zhí)行安全檢查;不要希望您的客戶會這樣做
            盡可能使用“可伸縮”常量
            盡可能使用標準庫構件

            提示

            定義項目范圍的全局系統(tǒng)類型
            使用 typedef 創(chuàng)建同義詞來加強局部含義
            使用冗余的圓括號使復合表達式含義更加清晰
            避免表達式的過深嵌套
            空指針使用 0 而不使用 NULL
            在異常首次出現(xiàn)時就報告它


            參考文獻

            [Cargill, 92] Cargill, Tom.1992. C++ Programming Styles Addison-Wesley.

            [Coplien, 92] Coplien, James O. 1992. Advanced C++ Programming Styles and Idioms, Addison-Wesley.

            [Ellemtel, 93] Ellemtel Telecommunications Systems Laboratories.June 1993. Programming in C++ Rules and Recommendations.

            [Ellis, 90] Ellis, Margaret A. and Stroustrup, Bjarne.1990.The Annotated C++ Reference Manual, Addison-Wesley.

            [Kruchten, 94] Kruchten, P. May 1994. Ada Programming Guidelines for the Canadian Automated Air Traffic System.

            [Lippman, 96] Lippman, Stanley, B. 1996. Inside the C++ Object Model, Addison-Wesley.

            [Meyers, 92] Meyers, Scott.1992. Effective C++, Addison-Wesley.

            [Meyers, 96] Meyers, Scott.1996. More Effective C++, Addison-Wesley.

            [Plauger, 95] Plauger, P.J. 1995. The Draft Standard C++ Library, Prentice Hall, Inc.

            [Plum, 91] Plum, Thomas and Saks, Dan.1991. C++ Programming Guidelines, Plum Hall Inc.

            [Rational, 92] Rational Software Corporation, December 1992. Rose C++ Programming Style Guidelines.

            [Stroustrup, 94] Stroustrup, Bjarne.1994. The Design and Evolution of C++, Addison-Wesley.

            [X3J16, 95] X3J16/95-0087 | WG21/N0687.April 1995. Working Paper for Draft Proposed International Standard for Information Systems-Programming Language C++.


            ? 1987 - 2001 Rational Software Corporation。版權所有。

            分欄顯示 Rational Unified Process

            Rational Unified Process??

            posted on 2006-04-24 10:09 楊粼波 閱讀(819) 評論(0)  編輯 收藏 引用 所屬分類: Windows編程

            久久精品国产99久久久古代| 国产成人无码精品久久久免费| 亚洲午夜久久久| 伊人色综合久久天天网| 亚洲AV无码久久精品色欲| 久久久久久夜精品精品免费啦 | 一本综合久久国产二区| 久久国语露脸国产精品电影| 精品久久久久久国产潘金莲| 国产亚洲美女精品久久久| 久久久久久精品久久久久| 99精品国产在热久久无毒不卡| 久久99精品九九九久久婷婷| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 久久AAAA片一区二区| 人妻无码αv中文字幕久久琪琪布| 少妇高潮惨叫久久久久久| 狠狠久久综合伊人不卡| 99久久精品国产一区二区 | 97热久久免费频精品99| 久久无码一区二区三区少妇| 久久精品国产亚洲AV无码偷窥| 久久99精品免费一区二区| 久久夜色精品国产网站| 麻豆久久| 国产精品99久久精品爆乳| 无码国产69精品久久久久网站| 久久久久无码中| 久久精品国产半推半就| 中文无码久久精品| 亚洲国产精品成人久久蜜臀| 久久av无码专区亚洲av桃花岛| 日韩久久久久中文字幕人妻| 久久综合欧美成人| 人妻无码αv中文字幕久久琪琪布| 精品久久久久久无码中文字幕 | 国产美女久久久| 日本欧美久久久久免费播放网| 欧美激情精品久久久久久久| 51久久夜色精品国产| 精品久久久久久成人AV|