午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

《Effective C++》簡明筆記-上

 quasiceo 2013-03-07

《Effective C++》簡明筆記-上

在學(xué)習(xí)算法導(dǎo)論的過程中,我深深地震撼于自己筆下C++代碼的丑陋。于是我決定捧 起這本《Effective C++》。本來打算看完這本書,寫一篇完整的筆記博文,但是剛剛看到一半,我已經(jīng)躍躍欲試地想動手改善我的代碼了。所以,我將寫完的這部分筆記整理成單獨 的一篇博文。

1. 視C++為一個語言聯(lián)盟。

  • C++ 包括 C & OO C++ & Template C++ & STL

2. 使用 const,enum,inline 代替#define。

3. 盡可能使用 const

  • const 修飾指針的不同含義
    char* const p1 = "hello"; // 固定指針:不能使p2指向其他對象
    const char* p2 = "hello"; // 固定數(shù)據(jù):不能修改p2指向的對象
  • const 修飾函數(shù)時的不同含義
    class Text
    {
    public:
        const std::size_t length() const; 
        // 返回文本的長度
        // 第一個 const 表示 函數(shù)返回一個常量,不可作為左值使用
        // 第二個 const 表示 函數(shù)不修改 Text 類中的數(shù)據(jù)成員(除了 static 和 mutable 修飾過的)
    private:
        char* data;
    };

    當(dāng)類的實例被聲明為 const 時,只能調(diào)用被第二個 const 修飾過的函數(shù)。

4. 保證使用對象前進行初始化

  • 內(nèi)置數(shù)據(jù)類型不進行初始化,所有對象類型都有默認(rèn)初始化函數(shù)。
  • 在構(gòu)造函數(shù)中對類成員賦值并不是真正意義的初始化,進入構(gòu)造函數(shù)體時,對象成員都已經(jīng)調(diào)用過默認(rèn)初始化函數(shù)了。應(yīng)當(dāng)使用初始值列進行初始化。
    class Person
    {
    public:
        Person():
          name(), // 調(diào)用 string 類默認(rèn)構(gòu)造函數(shù)
          sex_isMale(true) // 內(nèi)置類型,必須初始化
        {};
        Person(const std::string& tname, const bool& isMale):
          name(tname), // 調(diào)用 string 類的復(fù)制構(gòu)造函數(shù)
          sex_isMale(isMale)
        {};
    private:
        std::string name;
        bool sex_isMale;
    };
  • 不同編譯單元的 non-local static 對象(如全局對象)的初始化順序不可控,將對象放在一個全局函數(shù)中,并將對象其聲明為靜態(tài)成員。第一次調(diào)用該函數(shù)時必定會初始化該對象。

5. 了解C++默默做的事

  • 如果沒有聲明任何構(gòu)造函數(shù),則編譯器自動為類實現(xiàn)默認(rèn)構(gòu)造函數(shù)。
  • 如果你沒有實現(xiàn),編譯器會自動為類實現(xiàn)復(fù)制構(gòu)造函數(shù),復(fù)制運算符(operator=)函數(shù),析構(gòu)函數(shù)。
  • 如果類中包含引用類型的成員 或 const 成員,則編譯器不會實現(xiàn)復(fù)制運算符函數(shù)。因為更改 引用 或 const 成員是不允許的。

6. 如果不想使用編譯器自動生成函數(shù),就該明確拒絕

  • 將不想使用(如果你不聲明,編譯器就會自動生成)的函數(shù) 聲明 為 private,并且不實現(xiàn)它(防止友元類調(diào)用)。
  • 聲明基類,并在基類中將不想使用的函數(shù)聲明為 private,且不實現(xiàn)。繼承基類的派生類,編譯器不會自動生成相應(yīng)函數(shù)。
    class Uncopyble
    {
    protected:
        Uncopyble(){}
        ~Uncopyble(){}
    private:
        // 聲明但不實現(xiàn)復(fù)制構(gòu)造函數(shù),其派生類無法調(diào)用基類的復(fù)制構(gòu)造函數(shù)(由于private)
        // 因此編譯器無法自動生成派生類的復(fù)制構(gòu)造函數(shù)(默認(rèn)的邏輯上,該函數(shù)應(yīng)當(dāng)調(diào)用基類的復(fù)制構(gòu)造函數(shù))
        Uncopyble(const Uncopyble&); 
        // 復(fù)制操作符函數(shù)同理
        Uncopyble& operator=(const Uncopyble&); 
    };

7. 為多態(tài)基類聲明virtual析構(gòu)函數(shù)

  • 如果基類的析構(gòu)函數(shù)不是虛函數(shù),那么通過基類指針引用的派生類對象,在其銷毀時,只能銷毀積累部分,而不能銷毀派生類部分。

8. 不讓異常逃離析構(gòu)函數(shù)

  • 析構(gòu)函數(shù)往往并不由類的使用者親自調(diào)用,因此在析構(gòu)函數(shù)中拋出的異常難以捕捉。
  • 如果在對象的銷毀階段確實可能拋出異常(比如,由于網(wǎng)絡(luò)原因,關(guān)閉遠程數(shù)據(jù)庫失?。?,應(yīng)該另外實現(xiàn)一個使用者親自調(diào)用的銷毀函數(shù)如 close(),在該函數(shù)中拋出異常,以此給用戶以機會處理異常。在析構(gòu)函數(shù)中,檢查用戶是否調(diào)用了銷毀函數(shù):如果用戶已經(jīng)調(diào)用過,則不再調(diào)用該函數(shù);如 果用戶未曾調(diào)用過,則調(diào)用該函數(shù),在出現(xiàn)異常的情況下,并吞下異常或直接停止程序(用戶沒有立場抱怨,因為我們已經(jīng)給了他機會)。

9. 不在構(gòu)造函數(shù)或析構(gòu)函數(shù)中調(diào)用virtual函數(shù)

  • 派生類初始化時,先對基類部分初始化,然后才是派生部分?;惖臉?gòu)造函數(shù)運行時,派生類還不存在,此時調(diào)用虛函數(shù)并試圖完成派生類中相應(yīng)地邏輯:如果該虛函數(shù)有實現(xiàn),就僅僅調(diào)用虛函數(shù)而不是派生類中的函數(shù);如果沒有定義虛函數(shù),會出現(xiàn)連接錯誤。
  • 析構(gòu)函數(shù)同理。

10. 令 operator= 返回一個對 this 的引用

  • 這樣就可以使用連等式來賦值了。

11. 在 operator= 中處理自我賦值

  • 在 operator= 中需要考慮參數(shù)就是自身的情況,也要注意語句順序,以保證“異常安全性”。

12. 復(fù)制對象時不要忘了對象的每一個部分

  • 如果自己實現(xiàn)復(fù)制構(gòu)造函數(shù)和復(fù)制運算符函數(shù)(而不使用編譯器自動生成的版本),一定要記得將所有的成員都復(fù)制過來,編譯器不會因為這是個復(fù)制構(gòu)造函數(shù)或operater=而幫你檢查。
  • 如果你在派生類中自己實現(xiàn)以上兩種函數(shù),一定要記得顯式地調(diào)用基類的相應(yīng)函數(shù),編譯器不會幫你調(diào)用。
    class Person{
    public:
        Person(){}
        Person(const std::string& tname):name(tname){}
    private:
        std::string name;
    };
    class Citizen:public Person{
    public:
        Citizen():Person(),married(false){}
        Citizen(Citizen& pcitizen):
          Person(pcitizen), // 顯式調(diào)用基類的復(fù)制構(gòu)造函數(shù), 
                            // 注意傳入的是pcitizen而不是pcitizen.name,
                            // 因為調(diào)用的是基類的復(fù)制構(gòu)造函數(shù)而不是構(gòu)造函數(shù),
                            // 而且基類的private也不允許你這樣做
          married(pcitizen.married){} // 派生類部分的初始化
    private:
        bool married;
    };

13. 以對象管理資源

  • 所謂資源,往往是由 new 運算符產(chǎn)生的,由指針控制和管理的對象和數(shù)組。它們通常分配在堆(而不是棧)上,所以程序流程發(fā)生變化時,這些對象和數(shù)組不能自動銷毀(而分配在棧上的對象是可以的),需要手動銷毀。
  • RAII:對象的取得時機就是最好的初始化時機,兩種常用的RAII對象(智能指針):std::auto_ptr<T>和std::tr1::shared_ptr<T>,前者的復(fù)制方案為“轉(zhuǎn)讓所有權(quán)”,后者的復(fù)制方案為“計數(shù)器”。
  • 一個RAII對象示例
    class FontHandle;
    
    class Font{
    public:
        Font(FontHandle* ft):
          f(ft){}
        ~Font(){delete f;}
    ...
    private: FontHandle* f; };

    Font類的實例并不分配在堆上,但其指針成員 f 指向的對象 *f 分配在堆上。當(dāng)流程變化時,F(xiàn)ont 實例被正常銷毀,其析構(gòu)函數(shù)被調(diào)用,析構(gòu)函數(shù)中將指針成員指向的對象銷毀。這就保證了 *f 沒有泄露。

14. 在資源管理器中小心 copying 行為

  • 資源管理器的資源:即指針指向的對象,由資源管理器維護。當(dāng)自己實現(xiàn)智能指針對象時,考慮一下四種 copying 行為。
    • 禁止復(fù)制
    • 引用計數(shù)(如shared_ptr,需用到類的靜態(tài)成員)
    • 深度復(fù)制
    • 轉(zhuǎn)讓所有權(quán)(如auto_ptr)
  • 考慮著四種 copying 行為的目的就是,避免在析構(gòu)函數(shù)中多次試圖銷毀指針?biāo)笇ο?,或者完全不銷毀。

15. 在資源管理器中提供對原始資源的訪問

  • 往往對 RAII 對象實現(xiàn) operator-> 和 operator* 以實現(xiàn)對資源對象內(nèi)部成員的訪問。
  • 實現(xiàn)顯式轉(zhuǎn)換函數(shù),如 Font.get() 返回資源對象。
  • 實現(xiàn)隱式轉(zhuǎn)換函數(shù),如 Font::operator FontHandle() 返回資源對象。此時,F(xiàn)ont 對象 可 隱式轉(zhuǎn)換為 FontHandle 對象,但也會帶來部分風(fēng)險。
    class Font{
    public:
        Font(FontHandle* ft):
          f(ft){}
        ~Font(){delete f;}
        operator FontHandle(){return *f;}
        FontHandle get(){return *f;}
        ...
    private:
        FontHandle* f;
    };

16. 成對使用 new 與 delete 時采取相同的形式

  • 事實上,編譯器中實現(xiàn)了兩種指針,指向單個變量/對象 的 和指向變量/對象 數(shù)組的。使用 new 和 delete 時應(yīng)當(dāng)采取對應(yīng)的形式。
    std::string* s1 = new std::string("hello");
    std::string* s2 = new std::string[100];
    ...
    delete s1;
    delete [] s2;

17. 以獨立語句將 newed 對象置入智能指針中

  • 考慮這樣做:
    Font f1(new FontHandle);

    獨立語句的含義是:不將該語句拆開,也不將其合并到其他語句中,這樣可以確保資源不被泄露,如:

    // 不將其拆開
    FontHandle* fh1 = new FontHandle;
    ... // 發(fā)生異常怎么辦?
    Font f1(fh1);
    
    // 不將其合并
    AnotherFunction(Font(new FontHandle), otherParameters /*發(fā)生異常怎么辦?*/);

18. 讓接口易于使用,難于誤用

  • 讓接口易于使用,一般來說,就是盡量保持與內(nèi)置類型(甚至STL)同樣的行為。比如,你應(yīng)當(dāng)為 operator+ 函數(shù)返回 const 值,以免用戶對計算結(jié)果進行賦值操作,內(nèi)置類型不允許(對 int 型變量,語句 a+b=c 不能通過編譯,所以你的類型也應(yīng)該盡量保持同樣的性質(zhì),除非你有更好的理由);又比如,對象的主要組成部分如果是一個數(shù)組,那么數(shù)組的長度的成員名最好使 用 size 而不是 length,因為 STL 也這么做了。
  • 讓借口難于誤用,包括在類中限制成員的值(比如 Month 類型不可能表示 13 月),限制類型上的操作,在工廠函數(shù)中返回智能指針。

19. 設(shè)計 class 猶如 設(shè)計 type

20. 用 pass-by-reference-const 替換 pass-by-value

  • 為函數(shù)傳遞參數(shù)時,使用使用 const 引用傳遞變量。在定義函數(shù)時:
    class Person{...};
    class Citizen:public Person{...};
    
    bool validatePerson(Person psn){...} // 值傳遞,盡量不要這樣做
    bool validatePerson(const Person& psn){...} // const引用傳遞
  • 使用const引用類型傳遞函數(shù)參數(shù)的好處在于:
    • 免去不必要的構(gòu)造開銷:如果使用值傳遞,實參到形參的過程調(diào)用了類型的復(fù)制構(gòu)造函數(shù),而引用不會。
    • 避免不必要的割裂對象:如果函數(shù)的參數(shù)類型是基類,而函數(shù)中又調(diào)用了派生類中的某種邏輯(即調(diào)用了基類中的虛函數(shù)),那么值傳遞的后果就是,形參僅僅是個基類對象,虛函數(shù)也僅僅就調(diào)用了虛函數(shù)自己(而不是派生類中的函數(shù))。
    • 對于C++內(nèi)置類型和STL迭代器,使用值傳遞,以保持一致性。

21. 必須返回對象時,不要試圖返回 reference

  • 考慮一個有理數(shù)類:
    class Rational{
    public:
        Rational(int numerator=0, int denominator=1):n(numerator),d(denominator){}
    private:
        int n, d;
    };
  •  任何有理數(shù)可用分?jǐn)?shù)表示, n 和 d 分別為分子和分母,他們都是 int 型的。現(xiàn)在考慮為該類實現(xiàn)乘法,我們希望它能像內(nèi)置類型一樣工作。

    Rational x = 2;
    Rational y(1,3);
    Rational z = x*y*y; // z等于2/9
  • 我們可能會令函數(shù)返回引用類型(尤其是意識到20條中關(guān)于值傳遞的種種劣跡后):
    class Rational{
        ... 
    private:
        // 錯誤的代碼
        friend const &Rational operator* (const Rational& lhs, const Rational& rhs){
            Rational result(lhs.n*rhs.n, lhs.d*lhs.d);
            return result;
        }
        ...
    };

     result對象在 operator* 函數(shù)結(jié)束后就銷毀了,但我們返回了它的引用!這個引用指向 result 對象原先的位置(編譯器往往用指針實現(xiàn)引用),而且該位置在棧上!不僅無效,而且危險。

  • 我們也可能用new運算符建立一個新的對象(以防止在函數(shù)結(jié)束后被銷毀),并返回該對象的引用:
    // 錯誤的代碼
    friend const &Rational operator* (const Rational& lhs, const Rational& rhs){
            Rational* result = new Rational(lhs.n*rhs.n, lhs.d*lhs.d);
            return *result;
        }

     這次,*result 對象不會因為函數(shù)結(jié)束而銷毀了,它分配在堆上。但問題是,誰來負責(zé)銷毀它?尤其是上文 z=x*y*y 中,由 y*y 計算而得到的臨時變量,幾乎不可能正常銷毀。

  • 正確的做法是:
    // 正確的代碼
    friend const Rational operator* (const Rational& lhs, const Rational& rhs){
        return Rational(lhs.n*rhs.n, lhs.d*lhs.d);
    }

    雖然產(chǎn)生了構(gòu)造消耗,但這是值得的。返回的對象 z 分配在棧上,也就是說會在適當(dāng)?shù)臅r候銷毀,而原先函數(shù)中的臨時變量也正常銷毀了。

22. 將成員變量聲明為 private

23. 以 non-member 和 non-friend 函數(shù)替換 non-member 函數(shù)

  • 類的 public 方法越多,其封裝性就越差,內(nèi)部實現(xiàn)彈性就越小。設(shè)計類的時候應(yīng)由其細心。對于一些便利函數(shù)(這些函數(shù)往往只調(diào)用函數(shù)的其他 public 方法),可考慮將其放置在類外。C++允許函數(shù)單獨出現(xiàn)在類外,即使在C#等語言中,也可以使其出現(xiàn)在 工具 對象中。
  • 將類外的函數(shù)與類聲明在同一個命名空間中是不錯的選擇。

24. 如果函數(shù)的所有參數(shù)都需要類型轉(zhuǎn)換,采用 non-member 函數(shù)

  • 第21條中的代碼已經(jīng)體現(xiàn)出這一條的意思了。這一條大致就是希望 Rational 對象能像其他內(nèi)置對象一樣,直接參與運算。比如,希望這樣:
    Rational x(2,5);
    Rational y = x*2;
    Rational z = 2*x;
    • 首先,Rational 構(gòu)造函數(shù)沒有使用 explicit 修飾,這意味著 x*2 可以正常計算,因為這會調(diào)用 x.operator*(Rational& a),而整數(shù) 2 會隱式轉(zhuǎn)換成 Rational 對象。(等等,在第21條中我們好像沒有定義,x.operator*(Rational& a)函數(shù)?對,這是因為其中的代碼已經(jīng)遵循了本條的忠告,定義了 non-member 函數(shù)。)
    • 如果在 Rational 中定義了x.operator*(Rational& a),那么計算 z 時會遇到困難,因為系統(tǒng)會試圖調(diào)用 Int32.operator*(Rational& a),這根本沒有定義。所以,我們在代碼中并沒有定義成員函數(shù),而是定義了友元函數(shù) Rational operator*(Rational& a, Rational& b),正如在第21條的代碼中顯示的那樣。

25. 考慮寫一個不拋出異常的 swap 函數(shù)

  • std::swap 函數(shù)采取復(fù)制構(gòu)造的方法,效率比較低。
    namespace std{
        template <typename T>
        void swap(T& a, T& b){
            T temp(a);
            a = b;
            b = a;
        }
    }
  • 為自己的類實現(xiàn) swap 方法并 特化 std::swap

    class Person{
    private:
        void* photo;
    };
    
    namespace std{
        template <> // 特化std::swap方法
        void swap<Person>(Person& a, Person& b){
            std::swap(a.photo, b.photo);
        }
    }

    當(dāng)自己的類較大時,可在類中定義swap方法,并在 std::swap<YourClass> 中調(diào)用該方法。

26. 盡量延后變量定義式的時間

  • 僅當(dāng)變量第一次具有“具有明顯意義的初值”時,才定義變量,以避免不必要的構(gòu)造開銷。避免這樣做:
    std::string s; // 調(diào)用默認(rèn)構(gòu)造函數(shù)
    ...  // 如果發(fā)生異常呢,如果含有某個return語句呢?第一次調(diào)用構(gòu)造函數(shù)的開銷被浪費了
    s = "Hello"; // 再一次調(diào)用構(gòu)造函數(shù),第一次調(diào)用構(gòu)造函數(shù)的開銷依然被浪費了

     應(yīng)當(dāng)這樣做:

    std::string s("Hello"); // “hello”是具有明顯意義的初值,只調(diào)用了一次構(gòu)造函數(shù) 

27. 盡量少做轉(zhuǎn)型動作

  • 四種轉(zhuǎn)型動作
    • const_cast:消除對象的常量性
    • dynamic_cast:動態(tài)轉(zhuǎn)換,開銷較大。使用的場合往往是:想要在派生類上執(zhí)行派生類的某個函數(shù),但是手頭上只有基類的指針指向該對象。
    • reinterpret_cast:依賴于編譯器的低級轉(zhuǎn)型
    • static_cast:強迫隱式轉(zhuǎn)換,類似于C風(fēng)格的轉(zhuǎn)換,例如將int轉(zhuǎn)換為double等
  • 不要試圖在派生類的成員函數(shù)中,通過dynamic_cast將(*this)轉(zhuǎn)換為基類對象,并調(diào)用基類成員函數(shù)。
    class Person{
    public:
        void showMessage(){}
    };
    class Citizen:public Person{
    public:
        void showMessage(){
            dynamic_cast<Person>(*this).showMessage(); // 錯誤,這樣轉(zhuǎn)型得到的并不是期望的“基類對象
        }
    };

     而應(yīng)當(dāng)這樣做:

    Person::showMessage(); // 這就對了

28. 避免返回 handles 指向?qū)ο髢?nèi)部部分

  • handle 包括指針,引用,迭代器,用來獲取某個對象,以前被翻譯成句柄。
  • 在函數(shù)的方法中返回對象內(nèi)部成員的 handle 可能遭致這樣的風(fēng)險:返回的 handle 比對象本身更長壽,當(dāng)對象銷毀后,handle 所指向的區(qū)域就是不確定的。
  • string 和 vector 類型的 operator[] 就返回了對象內(nèi)部成員的 handle ,這只是例外。

29. 為“異常安全”而作的努力是值得的

  • 函數(shù)異常安全類型:
    • 基本承諾:如果異常拋出,程序內(nèi)的所有元素仍然在有效狀態(tài)下,沒有任何元素受到損壞(如釋放了指針指向資源卻沒有為其指定新的資源,該指針通向了不確定性)。
    • 強烈保障:如果異常拋出,程序內(nèi)的所有元素保持函數(shù)調(diào)用前的狀態(tài)。
    • 不throw異常:承諾絕不拋出異常。
    • 一個函數(shù)異常安全的程度取決于所調(diào)用函數(shù)中異常安全程度最弱的。
  • copy & swap 策略:為對象的數(shù)據(jù)制造一份副本,并對副本進行修改。如果發(fā)生異常,拋棄副本并返回;如果成功,則將對象數(shù)據(jù)與副本數(shù)據(jù)做 swap 操作,swap 操作承諾絕不拋出異常。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多