《Effective C++》第5章 實現-讀書筆記

來源:互聯網
上載者:User

標籤:

章節回顧:

《Effective C++》第1章 讓自己習慣C++-讀書筆記

《Effective C++》第2章 構造/析構/賦值運算(1)-讀書筆記

《Effective C++》第2章 構造/析構/賦值運算(2)-讀書筆記

《Effective C++》第3章 資源管理(1)-讀書筆記

《Effective C++》第3章 資源管理(2)-讀書筆記

《Effective C++》第4章 設計與聲明(1)-讀書筆記

《Effective C++》第4章 設計與聲明(2)-讀書筆記

《Effective C++》第5章 實現-讀書筆記

《Effective C++》第8章 定製new和delete-讀書筆記

 

條款26:儘可能延後變數定義式的出現時間

你定義了一個類類型的變數,那麼就要耗費一個建構函式和解構函式。如果你最終不使用這個變數,就應該避免這些耗費。

你可能會懷疑:怎麼可能定義一個變數而不去使用呢?考慮下面的代碼:

std::string encryptPassword(const std::string& password){    using namespace std;    string encrypted;    if (password.length() < MinimumPasswordLength)     {        throw logic_error("Password is too short")    }        ...    return encrypted;}

先不去考慮代碼具體含義。如果if語句為true,就會拋出異常,這個encrypted對象仍然需要耗費一個建構函式和一個解構函式。所以最好延後encrypted的定義式,直到確實需要它。

std::string encryptPassword(const std::string& password){    using namespace std;        if (password.length() < MinimumPasswordLength)     {        throw logic_error("Password is too short")    }        string encrypted;            //放在了後面    ...    return encrypted;}

這段代碼不夠穠纖合度(不懂這個詞)。因為encrypted對象調用的是預設建構函式,後面幾乎一定會對它重新賦值。舉例如下:

void encrypt(std::string& s);std::string encryptPassword(const std::string& password){    ...    string encrypted;            //放在了後面,考慮到使用時才定義        encrypted = password;        //重新賦值    encrypt(encrypted);    return encrypted;}

更好的做法是跳過無意義的default建構函式:

std::string encryptPassword(const std::string& password){    ...    string encrypted(password);            //拷貝建構函式    encrypt(encrypted);    return encrypted;}

所以,“儘可能延後”的真正意義是:你不僅要儘可能延後變數的定義直到要使用它,還應該延後這個變數的定義直到給它初值。這樣可以避免沒有必要的構造和析構對象以及沒有意義的default建構函式。

還有一種情形出現在迴圈裡面,分下下面兩種做法A、B哪個更好:

//做法AWidget w;        for (int i = 0; i < n; ++i){    w = 取決於i的某個值;    }//做法Bfor (int i = 0; i < n; ++i){    Widget w = 取決於i的某個值;    }

做法A的成本:1個建構函式、1個解構函式和n個賦值;做法B的成本:n個建構函式和n個解構函式。

如果賦值成本低於1個構造+1個析構,則做法A效率高一點,否則B的做法好。另外做法A造成Widget對象範圍擴大。所以,給出的建議是:除非你明確知道幾個操作的成本,否則做法B是比較好的。

請記住:儘可能延後變數定義式的出現。這樣做可增加程式的清晰度並改善程式效率。

 

條款27:盡量少做轉型動作

C風格的轉型(舊式轉型)如下:

(T) expression;        //兩者含義相同T(expression)

C++提供的4種新式轉型如下:

const_cast<T>(expression)dynamic_cast<T>(expression)reinterpret_cast<T>(expression)static_cast<T>(expression)

一般來說新式轉型比較好。可能舊式轉型比較常用的地方是調用explicit建構函式傳遞一個對象給函數時。舉例如下:

class Widget{public:    explicit Widget(int size);};void doSomeWork(const Widget& w);doSomeWork(Widget(15));                    //C的函數風格doSomeWork(static_cast<Widget>(15));    //C++新風格

任何一個類型轉換,無論是通過轉型操作進行的顯式轉換或通過編譯器進行的隱式轉換,往往會導致編譯器產生運行期執行的代碼。

下面有個轉型代碼比較有迷惑:

class Window{public:    Window(int n = 0) : m(n) {}    virtual void onResize()    {        m = 10;    }    int m;};class SpecialWindow : public Window{public:    virtual void onResize()    {        static_cast<Window>(*this).onResize();    }};int main(){    SpecialWindow w1;    cout << w1.m << endl;        //輸出0    w1.onResize();    cout << w1.m << endl;        //輸出0    return 0;}

兩份輸出都是0。不要懷疑,static_cast<Window>(*this).onResize();確實調用了class Window的onResize()函數,但關鍵是轉型的結果是(*this)的一個副本,而不是對象本身。

如果你仍然需要調用class Window版本的onResize()函數,就要拿掉轉型。

class SpecialWindow : public Window{public:    virtual void onResize()    {        Window::onResize();    }};

dynamic_cast的成本很高,之所以需要它的一個原因是:在一個你認定為derived class對象身上執行derived class函數,但你只有一個指向base的指標或引用。

請記住:

(1)如果可以,盡量避免轉型,特別是在注重效率的代碼中避免dynamic_cast。如果有個設計需要轉型動作,試著發展無需轉型的替代設計。

(2)如果轉型是必要的,試著將它隱藏於某個函數背後,客戶隨後可以調用該函數,而不需將轉型放在自己的代碼內。

(3)優先使用C++風格的轉型,因為它很容易被辨識出來並且有分類。

 

條款28:避免返回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; }            //返回右下角座標};

Rectangle類設計兩個成員函數upperLeft(),lowerRight()返回左上方和右下角座標是必要的。但這兩個函數都是const的,說明它的目的只是給使用者查看,並不是讓使用者去修改這些座標。但是客戶這樣做:

Point coord1(0, 0);Point coord2(100, 100);const Rectangle rec(coord1, coord2);rec.upperLeft().setX(50);                //左上方座標變為(50,0)

確實改變了座標值,儘管point還是private資料。這給我們的啟示是:成員變數的封裝性最多隻等於返回其reference函數的存取層級。雖然point是private的,但實際效果卻是public的。

修改版本也很簡單:

class Rectangle{public:    const Point& upperLeft() const { return pData->ulhc; }            //返回左上方座標的const    const Point& lowerRight() const { return pData->lrhc; }            //返回右下角座標的const};

另外一點handles指向的東西返回後可能不再存在。舉例說明:

class GUIObject { ... };const Rectangle boundingBox(const GUIObject& obj);//客戶如下這樣調用GUIObject *pgo;const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());

boundingBox(*pgo)的調用將產生一個臨時的Rectangle對象,在此對象上調用upperLeft()返回的是臨時對象的左上方座標,然後這個臨時對象析構,這樣pUpperLeft就是指向一個不存在的東西。

請記住:避免返回handles(包括引用、指標和迭代器)指向內部對象。遵守這個條款可增加封裝性、協助const成員函數的行為像個const,並降低發生handles指向不存在東西的可能性。

《Effective C++》第5章 實現-讀書筆記

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.