c++ effective總結(一)


條款一:視c++為一個語言聯邦

  c++可以認為是由C,Object-Oriented C++(面向對象),Template C++(模板),STL(c++標准模板庫)四種次語言組成的。

條款二:盡量以const,enum,inline替換#define

  c++中推薦使用其他的方法替換一些宏定義操作,如常量定義,推薦使用const int MAX = 10 替換#define MAX 10。而對於#define定義的函數,也推薦使用inline(內聯函數)替換。另外如在類中要聲明成員屬性的值。必須聲明成靜態常量,或者使用enum。如

class Stack{
public:
    ...
private:
    static const int size = 10; // 或者 enum { size = 10 };
    int members[size];
}

條款三:盡可能使用const

  const在c++里面可以說是用的非常多的一個修飾詞,const可以修飾值,指針,函數,函數參數,函數返回值等。

  如const char* p = "hello"; 指針指向的值為常量。char* const p = "hello";指向值的指針為常量。可以記為const在*左邊值為常量,const在*右邊指針為常量。如果*兩邊都有const,則值和指針都為常量。

  當你在使用stl容器時,使用迭代器訪問容器中的元素,而又不希望通過迭代器修改容器中的值,可以使用const_iterator,或者是遍歷常量容器對象,使用const_iterator。

  const修飾成員函數,該成員函數不可以修改成員屬性值,另外const常用來傳遞的引用參數,當你不希望修改引用參數中的內容時,可以用const修飾。

條款四:確定對象被使用前已先被初始化

  為內置對象進行手工初始化,因為c++不保證初始化它們,即你想初始化一個int對象,最好將int x;改寫成int x = 0;當然大部分情況下c++會為int x;分配 x = 0。

  構造函數初始化成員變量時,最好使用成員初值列,這樣效率比賦值要高一些,尤其是對非內置對象,內置對象兩者沒影響。如

//推薦使用
class Student{
public:
    Student(string& name, int age):name_(name),age_(age) {};
private:
    string name_;
    int age_;
}

//不推薦
class Student{
public:
    Student(string& name, int age) {
        name_ = name;
        age_ = age;
    };
private:
    string name_;
    int age_;
}

  上面第一種只有對於string對象只有copy操作,而第二種既有copy操作,還有賦值操作。

條款五:了解c++默默編寫並調用哪些函數

  你定義一個空類,編譯器會為它分配default構造函數,copy構造函數,copy assignment操作符合一個析構函數。如

class Empty {};

  實際上上面的類等價於

class Empty {
public:
    Empty() {...};
    Empty (const Empty& e) {...};
    ~Empty() {...};
    Empty& operator=(const Empty& e) {...};  
}

   如果你自己定義了構造函數,copy構造函數,copy assignment,析構函數,則編譯器不會再分配這些函數。

   編譯器分配的copy assignment有時候存在問題,如string& name這樣的成員屬性,編譯器分配的copy assignment是不可以賦值的,你需要自己重新定義copy assignment。

條款六:若不想使用編譯器自動生成的函數,就該明確拒絕

  如條款五所說編譯器會自動分配如copy構造函數,copy assignment這類的函數,如果你不想要這些函數,可以在private下聲明這些函數就行了。如

class Student{
private:
     Student (const Student&);
     Student& operator=(const Student&);
}

條款七:為多態基類聲明virtual析構函數

  帶有多態性質的基類,或者類中有帶vitural的成員函數,則其析構函數都應該定義為virtual析構函數。但如果該類不是設計為基類,則不要聲明virtual析構函數。如果基類中的析構函數沒有定義為vitural,則會出現一些問題,如在工廠模式中。有以下代碼

class Person{
public:
    Person();
    ~Person();
}

class Student : public Person {...};

Person* getPerson();

Person* student = getPerson();
delete student;

  假設當前的getPerson()獲取一個派生類Student對象,當使用完之后delete student時因為指針是基類指針,所以會銷毀基類中的成員,但是不會銷毀派生類中的成員,所以會存在”部分銷毀“的現象,但是若基類中的析構函數是vitural,則會刪除派生類中的成員。

  另外如果一個類不是基類,不要聲明vitural函數,因為vitural會引入虛表指針和虛表,會占用一部分內存。

條款八:別讓異常逃離析構函數

  這種通常指在析構函數中調用了一些函數,而這些函數可能引入異常,此時需要做一些處理,盡量保證不要讓析構函數的異常傳遞出來,或者說盡量確保析構函數中不要發生異常。

條款九:絕對不要在構造函數和析構函數中調用virtual函數

  因為上述這類調用絕對不會下降至下一層,即派生層,這種情況避免就好了。一般也不會這樣去調用

條款十:令operator= 返回一個reference to *this

  這是一種固定的assignment 操作符寫法,如

class Student{
public:
    Student& operator=(const Student& s){
        name_ = s.name_;
        age_ = s.age_;
        return *this;
    }
private:
    string name_;
    int age_;
}

  除了operator= 如operator+=等都可以寫成這樣。

條款十一:在operator= 中處理”自我賦值“

  自我賦值,即a = a;當然一般這種情況不會發生,但是這種*a = *b,而指針a和b都指向同一個值,這樣就存在問題。如果operator= 是下面這種寫法

class Bitmap {};

class Widget {
public:
    Widget& operator=(const Widget& rhs){
        delete pb;
        pb = new Bitmap(*rhs.pb);
        return *this;
    }
private:
    Bitmap* pb;
}

  如果rhs == this,則delete pb時也刪除了rhs中的pb。所以可以改寫成

class Bitmap {};

class Widget {
public:
    Widget& operator=(const Widget& rhs){
        Bitmap* pOrig = pb;
        pb = new Bitmap(*rhs.pb);
        delete pOrig;
        return *this;
    }
private:
    Bitmap* pb;
}

條款十二:復制對象時勿忘其每一個成分

  copy構造函數和copy assignment函數中不要忘了每一個成員變量,忘記了編譯器也不會報錯。如

class Student{
public: 
    Student (const Student& s):name_(s.name_) {};
private:
    string name_;
    int age_;
}

  派生類中的copy構造函數也不要忘了基類中的成員變量,可以直接調用基類的構造函數。

class Person{
public:
    Person (const Person& p):name_(p.name_), age_(p.age_) {};
private:
    string name_;
    int age_;
}

class Student : public Person {
public:
    Student (const Student& s) : Person(s), grade_(s.grade_){};
private:
    int grade_;
}

條款十三:以對象管理資源

  在c++中new和delete必須是同時存在的,但很多時候會忘記delete,或者說你很仔細的沒有忘記delete,但是在new和delete之間的代碼可能會存在return,continue等這類操作,而跳過了delete,為了防止這種內存泄漏的情況發生,所以也就有了以對象管理資源,當對象唄釋放時,對象的析構函數會自動釋放這些資源,如auto_ptr就是這種的資源管理對象。而c++11中的unique_ptr,shared_ptr也是這一類對象,只不過unique_ptr不能多個對象共享一塊內存,而shared_ptr通過引用計數機制,可以多個對象共享一塊內存,只有當引用計數為0才會釋放內存。

條款十四:在資源管理類中小心copying行為

  RAII(Resource Acquisition Is Initailization,資源取得時便是初始化時,也是以對象管理資源的概念)對象復制時要一並復制它所管理的資源。而普遍常見的RAII class copying行為是:抑制copying,施行引用計數法等。

 條款十五:在資源管理類中提供對原始資源的訪問

  APIs中往往需要訪問原始資源,所以每一個RAII class應該提供一個”取得其所管理之資源“的方法,如get成員函數可以獲得原始指針,重載了指針取值運算符,轉換到原始指針並取值。

  對原始資源的訪問可能經由顯示轉換或隱式轉換。一般而言顯式轉換比較安全,但隱式轉換對客戶比較方便。

條款十六:成對使用new和delete時要采取相同形式

  如下例子  

string* stringArray = new string[100];
...
delete stringArray;

  上面的new和delete並不是一個很好的應用,因為new出來的是一個數組,而delete很可能只釋放了一個元素,標准的用法是

string* stringArray = new string[100];
...
delete [] stringArray;

  所以說new和delete,new [] 和delete []。針對此問題要注意typedef的使用,最好不要對數組使用typedef,否則很容易在delete時忘了帶[]。

條款十七:以獨立語句將newed對象置入智能指針

  以獨立語句將newed對象存儲於智能指針內。如果不這樣做,一旦異常拋出,有可能導致難以察覺的資源泄漏。如

int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);

//調用
processWidget(std::shared_ptr<Widget> (new Widget), priority());

  在c++中上述執行的順序並不嚴格,可能先執行new Widget,再執行priority(),最后才執行shared_ptr的調用。這樣一旦priority()調用報錯,就不會發生new Widget的內存泄漏。所以最好獨立語句將對象放入智能指針。

std::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

條款十八:讓接口容易被正確使用,不易被誤用

  總結成一句話就是在設計接口時,盡量簡潔明了,約束性強,不容易發生誤用。

條款十九:設計class猶如設計type

條款二十:寧以pass-by-reference-to-const替換pass-by-value

  在函數的參數傳遞過程中對於非內置類型對象,盡量以引用或指針傳遞,推薦引用,為了避免函數中修改傳遞的對象,可以加上const修飾符。對於非內置類型,引用的傳遞要比傳值高效很多,因為傳值的過程中相當於傳遞副本,是需要調用copy構造函數,而函數執行完之后還會調用析構函數銷毀copy的對象,而引用傳遞不存在這一問題。

條款二十一:必須返回對象時,別妄想返回其reference

  在函數的返回值時切勿返回reference、pointer,尤其是指向函數中的局部對象,直接返回值即可,雖然這樣會耗時(調用構造函數),但至少是正確的。

條款二十二:將成員變量聲明為private

條款二十三:寧以non-member,non-friend替換member函數

條款二十四:若所有參數皆需類型轉換,請為此采用non-member函數

條款二十五:考慮寫出一個不拋異常的swap函數

條款二十六:盡可能延后變量定義式的出現時間

  定義的變量即使沒有被使用,也會存在構造和析構的操作,也就是或會存在構造和析構的成本,所以在定義變量時盡量在使用時定義。

條款二十七:盡量少做轉型動作

  如果可以,盡量避免轉型,特別是在注重效率的代碼中避免dynamic_casts。

  如果轉型是必要的,試着將他隱藏於某個函數背后。客戶隨后可以調用該函數,而不需將轉型放入到他們自己的代碼內。

  寧可使用c++風格的新式轉型,不要使用舊式轉型。

 條款二十八:避免返回handles指向對象內部成分

  對於下面的例子,返回引用(handles,指針,迭代器也可以歸為這一類)會導致內部數據的不安全.

class Point {
public:
    Point(int x, int y);
    void setX(int newVal);
    void setY(int newVal);
}

struct RectData{
    Point ulhc;
    Point lrhc;
}

class Rectangle{
public:
    Point& upperLeft () const {return pData->ulhc};
    Point& lowerRight () const {return pData->lrhc};
private:
    std::shared_ptr<RectData> pData;
}

  上面的Rectangle類中,雖然upperLeft函數是const函數,但是返回的結果是Point的引用,通過該引用是可以修改Point內部的值,包括返回指針和迭代器都是會發生類似的情況,所以如果硬要返回引用這類handles,可以返回const references

class Rectangle{
public:
    const Point& upperLeft () const {return pData->ulhc};
    const Point& lowerRight () const {return pData->lrhc};
private:
    std::shared_ptr<RectData> pData;
}

條款二十九:為”異常安全“而努力是值得的

條款三十:透徹了解inlining的里里外外

  總之只有在小型且頻繁被調用的函數身上才使用inlining。這樣可以保證代碼膨脹的問題最小化,程序的速度提升最大化,如max函數。

條款三十一:將文件間的編譯依存關系降至最低

條款三十二:確定你的public繼承塑模出is-a關系

  基類和派生類之間一定要有is-a關系,即基類的屬性和方法,在派生類中一定是合理存在的。

條款三十三:避免遮掩繼承而來的名稱

  只要熟悉作用域,根據作用域去定義變量就可以了,知道在使用變量時作用域的先后順序。

條款三十四:區分接口繼承和實現繼承

  這里涉及到基類中的純虛函數,虛函數和非虛函數以及繼承的概念。

  純虛函數:含有純虛函數的類是抽象類,不可以被new出來。對於純虛函數,子類只繼承了接口,子類中必須聲明和實現純虛函數,當然父類中也可以定義純虛函數,但要指定父類名稱才可以調用。

  虛函數:子類會繼承虛函數的接口和缺省實現,子類中也可以重寫虛函數。

  非虛函數:子類會繼承非虛函數的接口和強制實現,不建議子類中重寫非虛函數。如果硬要重寫,調用時基類指針會調用基類中的函數,想調用子類中的重寫函數,必須使用子類指針。

條款三十五:考慮virtual函數以外的其他選擇

  在子類繼承父類的功能時,可以引入一些設計模式來使得繼承更靈活。如使用模板模式,策略模式。

條款三十六:絕不重新定義繼承而來的非虛函數

  正如上面所說如果子類重寫了父類的非虛函數,父類指針即使是指向子類,當調用非虛函數時,還是會調用父類的非虛函數,所以非虛函數的調用和指針類型綁定。所以為了避免出現這樣的迷惑行為,還是不要重寫非虛函數。

條款三十七:絕不重新定義繼承而來的缺省參數值

  對於繼承而來的虛函數或純虛函數,如果函數中有定義的缺省參數值,繼承時不要修改缺省參數值,因為缺省參數值是靜態綁定(發生在編譯期)的。而虛函數或純虛函數時動態綁定(發生在運行期)的。

條款三十八:通過復合塑模出has-a或”根據某物實現出“

條款三十九:明智而審慎地使用private繼承

  private並不會繼承接口,而是繼承基類的實現,總之盡量使用public,除非特殊實現上,因為private繼承本質上不屬於is-a關系。

條款四十:明智而審慎地使用多重繼承

  總之能使用單一繼承的盡量使用單一繼承,多重繼承太過復雜,容易引入歧義,且應用於多重繼承的虛繼承既要提前設計也會引入大小、速度等成本。

條款四十一:了解隱式接口和編譯器多態

  c++中的模板創造了隱式接口和編譯期的多態。

條款四十二:了解typename的雙重意義

  在聲明模板時,template<typename T> 和 template<class T>的含義是一樣的,但是typename可以表示嵌套從屬類型,如T::setX到底是一個類型還是成員名稱,如果加上typename就很明確是一個類型了,typename T::setX。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM