C++的35個技巧閱讀筆記(五)


系列文章:
C++的35個技巧閱讀筆記(一)
C++的35個技巧閱讀筆記(二)
C++的35個技巧閱讀筆記(三)
C++的35個技巧閱讀筆記(四)
本次筆記爲More Effective 35最後的雜項部分

32.在未來時態下發展程序

  • 1、如果某個class在設計時,絕不打算成爲derived classes(派生類),那麼就不應該只是在頭文件的class上端擺一樣註釋就好,而是應該以C++語法來阻止派生(見條款26)。
  • 2、如果要求其所有對象實體都必須於heap內產生,應該以條款27厲行這項約束。
  • 3、請爲每一個class處理assignment 和copy constructor動作,即使沒有人使用,現在沒有不意味着將來沒有。
  • 4、請努力讓classes的操作符和函數擁有自然的語法和直觀的語義。請和內建類型的行爲保持一致:如有疑惑,請看ints有怎麼樣的表現。
  • 5、請讓你的classes容易被正確地使用,不容易被誤用,請接收“客戶會犯錯”的事實,並設計你的classes有預防、偵測或更正的能力。
  • 6、請努力寫出可移植代碼。
  • 7、請設計你的代碼,使“系統改變所帶來的衝擊”得以局部化。儘可能採用封裝性質,儘可能讓實現細目成爲private。儘量避免設計出virtual base classes,因爲這種classes必須被其每一個derived class初始化。
  • 8、提供完整的classes——即使某些部分目前用不到。
  • 9、設計你的接口,使有利於共同的操作行爲,阻止共同的錯誤。
  • 10、儘量使你的代碼一般化(泛化),除非有不良的巨大後果。儘量用“未來式思維”去思考,它可增加你的代碼重用性、加強其可維護性、使它更健壯,並促使在一個“改變實乃必然”的環境中有着優雅的改變。

33.將非尾端類(non-leaf classes)設計爲抽象類(abstract classes)

允許通過指針進行“同型賦值”而阻止“異型賦值”的方法:

  • 1、通過兩個賦值操作符重載,一個是virtual,一個是正常版本。該方法可以實現但是不推薦使用。
  • 2、讓operator=成爲Animal的private函數。
class Animal
{
private:
Animal& operatro=(const Animal& rhs);
...
};
class Lizard : public Animal {
public:
Lizard& operator=(const Lizard& rhs);
...
};
class Chicken : public Animal {
public:
Chicken& operator=(const Chicken& rhs);
...
};
 
Lizard liz1, liz2;
...
liz1 = liz2;                //很好
Chicken chick1, chick2;
...
chick1 = chick2;            //很好
 
Animal *pAnimal1 = &liz1;
Animal *pAnimal2 = &chick1;
...
*pAnimal1 = *pAnimal2;            //錯誤,企圖調用private Animal::operator=
 
不幸的是,Animal是一個具體類,而上述方法卻使得Animal對象彼此間的賦值動作也不合法:
Animal  animal1,animal2;
animal1 = animal2;

正確的做法是:消除“允許Animal對象相互賦值”的需要,而完成此事的最簡單做法是讓Animal成爲一個抽象類,Animal就無法被實例化。如果當初設計Animal要相互賦值,則改一個新的抽象類AbstractAnimal。

class AbstractAnimal
{
protected:
    AbstractAnimal& operator=(const AbstractAnimal& rhs);
public:
    virtual ~AbstractAnimal() = 0;        //它必須內含至少一個純虛函數,一般讓destructor成爲純虛函數
...
};
class Animal : public AbstractAnimal
{
public:
    Animal& operatro=(const Animal& rhs);
...
};
class Lizard : public AbstractAnimal {
public:
    Lizard& operator=(const Lizard& rhs);
...
};
class Chicken : public AbstractAnimal {
public:
    Chicken& operator=(const Chicken& rhs);
...
};

將函數“聲明爲純虛函數”並非暗示它沒有實現碼,而是意味着:

  • 1、目前這個class是抽象的。
  • 2、任何繼承此class的具體類,都必須將純虛函數重新聲明爲一個正常的虛函數(也就是說,不可以再令它 = 0)。

如果是程序庫中的具體類你要繼承,( 從類庫的實體類派生一個新類)有如下做法:

  • 1、將具體派生自既存的(程序庫中的)具體類,但需要注意本條款一開始所驗證的assignment相關問題,並且小心條款3所描述的數組相關陷阱。
  • 2、試着在程序庫的繼承體系中找到一個更高層的抽象類,其中你需要大部分功能,然後繼承它。
  • 3、以“你所希望繼承的那個程序庫類”來實現你自己的新類,例如你可以令程序庫類的一個對象成爲你的data member,然後在你的新類中重新實現該程序庫類的接口。
  • 4、使用類庫的類,修改自己的程序增加非成員函數,不重寫派生類。

34.如何在同一個程序中結合 C++ 和 C

  • 1、name mangling(名稱重整),C中你的函數名稱不能重載。要壓抑name mangling,必須使用C++的extern "C"指令:
extern "C" {
void drawLine(int x1, int y1);
void simulate(int iterations);
}
  • 2、Static 的初始化,一般在main函數最開始端插入一個static對象初始化構造操作,在最尾端安插一個static 對象的析構。
  • 3、動態內存分配,程序的C++部分使用new 和delete,程序的C部分使用malloc(及其變種)和free。
  • 4、數據結構的兼容性,將兩個語言間的“數據結構傳遞”限制於C所能瞭解的形式;C++structs如果內含非虛函數,倒是不受此限。

35.讓自己習慣於標準C++語言

  • 在ARM(Annotated C++ Reference Manual)出版後的這些年,C++最重要的幾項改變如下:
    1、增加了一些新的語言特性:RTTI、namespace、bool、關鍵詞mutable 和explicit、enums最爲重載函數之自變量所引發的類型晉升轉換,以及在“class定義區內直接爲整數型const static class members設定初值”的能力。
    2、擴充了Templates的彈性:允許member template存在、接納“明白指示template當場實例化”的標準語法、允許function templates接受“非類型自變量”、可用class templates作爲其他template的自變量。
    3、強化了異常處理機制:
    4、修改了內存分配歷程:加入operator new[]和operator delete[],內存爲分配成功時候拋出異常,在內存失敗時返回0;
    5、增加了新的轉型形式:static_cast,dynamic_cast,const_cast 和 reinterpret_cast.
    6、語言規則更爲優雅精煉:重新定義虛函數時,其返回類型不再一定得與原定義完全吻合。

  • STL標準程序庫改變:
    1、支持C標準函數庫。
    2、支持strings。
    3、支持本地化。
    4、支持I/O。
    5、支持數值應用。複數等
    6、支持廣泛用途的container (容器)和 algorithm(算法)。

補充:auto_ptr實例代碼

  • auto_ptr實例代碼1
template<class T>
class auto_ptr {
public:
    explict auto_ptr(T *p = 0);
    template<class U>
    auto_ptr(auto_ptr<U>& rhs);
 
    ~auto_ptr();
    template<class U>
    auto_ptr<T>& operator=(auto_ptr<U>& rhs);
    T& operator*() const;
    T* operator->() const;
    T* get() const;
    T* release();
 
    void reset(T *p = 0);
private:
    T *pointee;
    template<class U>
    friend class auto_ptr<U>;
};
 
template<class T>
template<class U>
    inline auto_ptr<T>::auto_ptr(auto_ptr<U>& rhs) : pointee(rhs.release()) {}
 
template<class T>
    inline auto_ptr<T>::~auto_ptr() { delete pointee; }
 
template<class T>
template<class U>
    inline auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs)
    {
        if(this != &rhs) reset(rhs.release());
        return *this;
    }
 
template<class T>
    inline T& auto_ptr<T>::operator*() const { return pointee; }
 
template<class T>
    inline T* auto_ptr<T>::operator->() const { return pointee; }
 
template<class T>
    inline T* auto_ptr<T>::get() const { return pointee; }
 
template<class T>
    inline T* auto_ptr<T>::release()
    {
        T* oldPointee = pointee;
        pointee = 0;
        return oldPointee;
    }
 
template<class T>
    inline void auto_ptr<T>::reset(T*p)
    {
        if(pointee != p) {
            delete pointee;
            pointee = p;
        }
    }
  • auto_ptr實例代碼2
//所有函數都定義在class定義區內:
template<class T>
class auto_ptr {
public:
    explicit auto_ptr(T *p = 0) : pointee(p) {}
 
template<class U>
    auto_ptr(auto_ptr<U>& rhs) : pointee(rhs.release()) { }
    ~auto_ptr() { delete pointee: }
 
template<class U>
    auto_ptr<T>& operator=(auto_ptr<U>& rhs)
    {
        if(this != &rhs) reset(rhs.release());
        return *this;
    }
 
    T& operator*() const { return *pointee; }
    T* operator->()const { return pointee; }
    T* get() cosnt { return pointee; }
    T* release() 
    {
        T *oldPointee = pointee;
        pointee = 0;
        return oldPointee;
    }
 
    void reset(T *p = 0)
    {
        if(pointee != p) {
        delete pointee;
        pointee = p;
        }
    }
 
private:
    T* pointee;
    template<class U> friend class auto_ptr<U>;
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章