effective C++ 55 01-12筆記

來源:互聯網
上載者:User

條款01:視C++為一個語言聯邦 : C, Object-Oriented C++, Template C++, STL

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

1.對於單純常量,最好以const對象或enum替換#define

2.對於形似函數的宏(macros),最好改用inline函數替換#define

#define ASPECT_RATIO 1.653

const double AspectRatio = 1.653;

        1)用#define定義的名字可能沒有進入記號表,編譯出錯,不方便追蹤

        2)用const定義常量將得到更精簡的目標代碼(object code),因為前置處理器處理#define是會盲目的將宏名稱替換為1.653導致目標碼出現多份1.653若用const則不會出現這種問題。

        當用常量替換#define有兩種特殊的情況。

       1)定義常量指標。由於常量定義式通常放在標頭檔內(以便被不同源碼含入),因此有必要將指標聲明為const。

        const char* const authorName = “Scott Meyer”;

        string對象通常比char*-base合宜,所以authorName往往定義成這樣:const std::string authorName(“Scott Meyer”);

       2)class專屬常量。為了將常量的範圍限制於class內,和確保此常量至多隻有一份實體,必須讓它成為一個static成員:

class GamePlayer {

private:

   static const int NumTurns = 5;

    int scores[NumTurns];

    ...

}

通常static & const& 整數類型(ints,chars,bools)類型成員如果不取地址,可以聲明並使用而無需定義。但如果你取某個class專屬常量的地址,或你的編譯器卻(不正確地)堅持要看到一個定義式就必須提供如下定義式

const int GamePlayer::NumTurns;   //放到實現檔案,因為在聲明時已獲得初值,不能再設初值。

我們無法用#define 建立class專屬常量,因為#define不重視範圍scope。

舊時編譯器也許不支援上述文法,不允許static成員在聲明時提供初值。

則將初值放到定義式裡,但編譯器堅持必須在編譯期間知道數組score[]大小,萬一你的編譯器不允許static 整數型class常量完成 in class初值設定,可改用 the enum hack補償做法。

class GamePlayer{

private:

    enum{ NumTurns=5 };

    int scores[NumTurns];

}

1)取enum和#define地址一樣,不合法。如果你不想讓別人獲得一個pointer或reference指向你的某個整數常量,enum可以協助你實現這個約束。enums和#defines一樣不會導致非必要的記憶體配置。

2)實用,很多代碼用了。 enum hack是 模板元編程template metaprogramming的基礎技術。

另一個常見的#define誤用情況是用它實現宏。宏看起來像函數,但不會招致函數調用帶來的額外開銷。

#define MAX(a,b) f((a)>(b)?(a):(b))
int a =5, b = 0;
MAX(++a, b);//a被累加二次
MAX(++a, b+10);//a被累加一次  

替換為內聯模板函數:宏的效率和型別安全,可預料行為
template<typename T>
inline void MAX(const T&a,const T&b)
{
      f(a>b?a:b);
}

條款03: 儘可能實用const

const Rational operator* (const Rational& lhs, const Rational& rhs);//為什麼返回const對象?

Rational a, b, c;

(a * b) = c;//

if(a * b = c) … // 其實想做比較操作,返回const可預防此類問題

const成員函數:可被const對象調用。兩個函數如果只是常量性不同,是可以被重載。

可用mutable釋放掉non-static成員變數的bitwise constness約束(即mutable修飾的成員變數可被const成員函數修改)。

在const和non-const成員函數中避免重複:

class TextBlock {

public: …

  const char& operator[] (std::size_t position) const {

   …

   return text[position];

}

char& operator[] (std::size_t position) {   //non-const成員函數調用const成員函數避免代碼重複

return

          const_cast<char&>(

                  static_cast<const TextBlock&>(*this)

                       [position]);

}

private: std::string text';

}; 

不能令const成員函數調用non-const成員函數,對象有可能被改動。

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

1.對內建類型手工初始化

2.建構函式最好使用成員初值列 member initialization list,而不要在建構函式體內用賦值操作。初值列中列出的成員變數次序要跟在class中聲明次序一致。

3.為免除跨編譯單元之初始化次序問題,用local static 對象替換non-local static 對象

class A {

public: A(const string&);

private:

            string str;

             int num;

}

A::A(const string &s){

str=s;   //賦值而非初始化,在初值列中調用一次default建構函式,函數體內一次賦值操作

num=0'; //內建類型不保證。

}

C++規定對象的成員變數初始化動作發生在進入建構函式體之前。如果成員變數是const或引用,則一定要初始化,而不能被賦值。

A::A(const string &s):str(s),   //值調用一次copy建構函式

                                       num(0){}  

static對象:從被構造到程式結束為止。main()結束時自動析構。

包括global對象,namespace內的對象,在class內、函數內、及在file範圍內被聲明為static的對象。

local static對象:函數內的static對象

non-local static對象:其他

如果某個編譯單元內的某個non-local static對象的初始化動作使用了另一編譯單元內的某個non-local static對象,則可能未被初始化。C++對”定義於不同便一單元內的non-local static對象“的初始化次序無明確定義。

class FileSystem {

public:  …

              std::size_t numDisks() const;

     };

extern FileSystem tfs;

//另一編譯單元

class Directory {

public:

         Directory(params);

};

Directory::Directory(params) {

      std::size_t disks = tfs.numDisks();

}

Directory tempDir (params);

解決方案:singleton模式,用local static對象替換non-local static對象

C++保證,函數內的local static 對象會在該函數被調用期間,首次遇上該對象定義式時被初始化。

class FileSystem {…};

FileSystem& tfs() {

     static FileSystem fs;

     return fs;}

class Directory {…};

Directory::Directory (params) {

     std::size_t disks = tfs().numDisks();

};

Directory& tempDir() {

    static Directory td;

    return td;}

條款05:瞭解C++默默編寫並調用哪些函數

編譯器暗自為class建立default建構函式,copy建構函式,copy assignment操作符,以及解構函式

只有當一個類沒有定義建構函式時,編譯器才會自動產生一個預設建構函式。

如果沒有定義複製建構函式,編譯器就會合成一個,即使已經定義了其他的建構函式。

template<class T>

class NamedObject {

public: NamedObject(string &name, const T &value):nameValue(name),objectValue(value){}

private: string &nameValue;

             const T objectValue;

};

string newDog(“aaa”);

string oldDod(“bbb”);

NamedObject<int> p(newDog,2);

NamedObject<int> s(oldDog, 36);

p = s; //編譯失敗

在內含引用或const成員的class內支援賦值操作符,你必須自己定義copy assignment操作符(copy建構函式內不是賦值)。

如果某個base class將copy assignment操作符聲明為private,編譯器拒絕為其derived classes產生一個copy assignment操作符。

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

方案:將相應成員函式宣告為private並不予實現。或使用Uncopyable這樣的base class

明確聲明一個成員函數,阻止編譯器暗自產生,另其為private,阻止外部調用。

但成員函數和friend仍然可以反問,所以只聲明而不定義它們,如不慎調用,則會得到一個串連錯誤。

將串連期錯誤移到編譯器:

class Uncopyable{

protected: Uncopyable(){}              //允許derived物件建構和析構

                     ~Uncopyablel(){}

private: Uncopyable(const Uncopyable&);    //但阻止copying

               Uncopyable& operator=(const Uncopyable&);

};

class HomeForSale:private Uncopyable { …};

當嘗試拷貝HomeForSale對象時,自動產生copy建構函式和copy assignment操作符,並嘗試調用基類對於版本,但失敗。。。

條款07: 為多態基類聲明virtual解構函式

1.若果class帶有任何virtual函數,它就應該擁有一個virtual解構函式

2.class的設計目的不是作為base class使用或不是為了具備多態性,就不改聲明為virtual解構函式

C++明確指出當derived對象經過base class指標被刪除,而該base class帶有一個non-virtual解構函式,則其結果未定義的:局部銷毀。


一個帶有virtual函數的class都有一個相應的由函數指標構成的數組vtbl(virtual table).
當對象調用某一virtual函數,實際被調用的函數取決於該對象的vptr (virtual table
pointer)所指的那個vtbl,編譯器在其中尋找適當的函數指標。無端聲明解構函式為virtual會增加為vptr的付出的空間補償。

class SpecialString: public std::string{   //餿主意!

};

SpecialString *pss = new SpecialString(“aaa”);

std::string *ps=pss;

delete ps;//未定義!

如果企圖繼承一標準容器或任何其他帶有non-virtual解構函式的class,拒絕誘惑吧!!!

為你希望成為抽象(不能實體化)的那個class聲明一個pure virtual解構函式

class AWOV {

public: virtual ~AWOV() = 0;

};

必須提供一份定義 AWOV::AWOV() {}

不然衍生類別的解構函式調用base class的解構函式時,不然連接器會報錯。

條款08:別讓異常逃離解構函式

1.解構函式絕對不要吐出異常。如果一個被解構函式調用的函數可能突出異常,解構函式應該捕捉任何異常,然後吞下(不傳播)或結束程式。

2.如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那麼class應該提供一個普通函數(而非解構函式中)執行該操作。

棧展開期間,釋放局部對象所用的記憶體並運行類類型局部對象的解構函式。在執行解構函式前如果已經有一個異常,解構函式中又拋出一個異常,將會導致調用標準庫 terminate 函數強制從程式非正常退出。

class DBConnection {

public : static DBConnection create();

              void close();

};

確保DBConnection不忘記調用close()

class DBConn {

public:

         ~DBConn(){

                     db.close();}

private: DBConnection db;

};

DBConn dbc(DBConnection::create());

如果解構函式拋出異常:

1) 強迫結束

DBConn::~DBConn(){

try { db.close();}

catch(…) {

          製作運轉記錄,記下對close的調用失敗

          std::abort();

}

2)吞下異常

DBConn::~DBConn(){

try { db.close();}

catch(…) {

製作運轉記錄,記下對close的調用失敗

}

3)較佳的策略,客戶自己調用普通函數

class DBConn {

public:

void close(){

    db.close();

   closed = true;

}

~DBConn(){

    if(!closed) {

         try { db.close(); }

         catch(…){

                  製作運轉記錄,記下對close的調用失敗

                 abort或吞下異常

                    }

    }

private: DBConnection db;

               bool closed;

};

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

條款11: 在operator= 中處理自我賦值

class Bitmap {…};

class Widget {

private: Bitmap *pb;

};

Widget& Widget::operator=(const Widget &rhs) {      

     delete pb;                            //沒有進行自我賦值檢測

     pb = new Bitmap(*rhs.pb);//不具備異常安全,發生異常後,pb指向一塊已刪除的記憶體

     return *this;

}

解決:證同測試

Widget& Widget::operator=(const Widget &rhs) {//不具備異常安全

        if (this == &rhs) return *this;

        delete pb;

        pb = new Bitmap(*rhs.pb);//不具備異常安全

        return *this;

}

Widget& Widget::operator=(const Widget &rhs) {

        Bitmap *pOrig = pb;  //異常安全,又能處理自我賦值,當自我賦值發生頻率較高時,可加入證同測試。

        pb = new Bitmap(*rhs.pb);

        delete pOrig;

        return *this;

}

另一替換方案確保異常安全,又能處理自我賦值:copy and swap技術

class Widget{

      void swap(Widget &rhs);

};

Widget& Widget::operator=(const Widget &rhs){

     Widget temp(rhs);

     swap(temp);

     return *this;

}

Widget& Widget::operator=(const Widget rhs){ //另一較高效寫法,by-value

     swap(rhs);

    return *this;

}

條款12: 複製對象時勿忘其每一個成分

1.copying 函數應該確保賦值”對象內所有成員變數”及“所有base class成分”

2.不要嘗試以某個copying函數實現另一個copying函數。應該將共同機能放進第三個函數中,並由兩個函數共同調用。

為derived class編寫copying函數時,必須複製其base class成分。

class Customer {

public:

          Customer(const Customer &rhs);

          Customer& operator=(const Customer &rhs);

private: string name;

};

class PriorityCustomer: public Customer {

public:

          priorityCustomer(const priorityCustomer &rhs);

          priorityCustomer& operator=(const priorityCustomer &rhs);

private: int priority;

};

priorityCustomer::priorityCustomer(const priorityCustomer &rhs)

:Customer(rhs),  //調用base class的copy建構函式,如省略,則調用預設的建構函式,基類部分為預設值

priority(rhs.priority)

{ }

priorityCustomer& priorityCustomer::operator=(const priorityCustomer &rhs) {

        Customer::operator=(rhs);    //可先進行證同測試,成員如有指標

        priority = rhs.priority;  

        return *this;

}

【轉載】http://www.cnblogs.com/Atela/archive/2011/04/05/2006121.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.