1. 程式人生 > 實用技巧 >Effective C++ 筆記(1)

Effective C++ 筆記(1)

第一部分: 讓自己習慣C++

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

請記住

-C++高效程式設計守則視狀況而變化,取決於你使用C++的哪一部分。


條款02,03:儘量以const, enum, inline替換#define,儘可能使用const

對於指標變數的const

對於指標變數的const, 根據const和星號的相對位置,有不同的行為
左定值,右定向

const對於迭代器

//iter的作用像個T* const
const std::vector<int>::iterator iter = vec.begin();
//cIter的作用像個const T*
std::vector<int>::const_iterator cIter = vec.begin();

const與函式宣告

class Rational (...);
const Rational operator *(const Rational& lhs, const Rational& rhs);

Rational之前的const表示函式返回一個常量值,用以避免這樣的暴行

(a*b)=c;//如果沒有const,不報錯,有const報錯

const成員函式

\(bitwise\) \(const\)宣告
在成員函式的括號後面加const,告訴編譯器我不會修改這個物件的任何成員變數。
然而有些成員函式不具有const性質,卻能通過bitwise測試

class CTextBlock {
    public:
    ...
    char &operator [](std::size_t position) const {//bitwise const宣告
        return pText[position];
    }
    private:
    char *pText;
}

通過了bitwise const 測試,卻允許發生這種事

const CTextBlock cctb("Hello");
char *pc = &cctb[0];
*pc = 'J';

\(logical\) \(const\)
一個const成員函式可以修改物件內的某些bits,只要客戶端偵測不出來

mutable字首修飾的變數可以在const成員函式中被修改

class CTextBlock{
    public:
        ...
        std::size_t length() const;
    private:
        char* pText;
        mutable std::size_t textLength;
        mutable bool lengthIsValid;
};
//logical const
//雖然有修改,但是修改的是mutable變數,所以通過bitwise測試
std::size_t CTexeBlock::length() const {
    if(!lengthIsValid) {
        textLength = std::strlen(pText);
        lengthIsValid = true;
    }
    return textLength;
}

在const和non-const中避免重複

對於 non-const 版本的函式可以採取呼叫 const 版本的函式的方法來實現。(避免程式碼重複)

class TextBlock {
   public:
    const char &operator[](int i) const { return text[i]; }
    char &operator[](int i) { return const_cast<char &>(static_cast<const TextBlock &>(*this)[i]); }
    TextBlock(std::string s) : text(s) {}

   private:
    std::string text;
};

這裡使用的const_caststatic_cast型別轉換。
值得注意的是,反向做法是不應該的。const 成員函式承諾絕不改變其物件的邏輯狀態,但是 non-const 成員函式沒有承諾,在 const 成員函式內呼叫 non-const 成員函式是有風險的。

請記住
  • 將某些東西宣告為 const 可幫助編譯器偵測出錯誤用法。const 可被施加於任何作用域的物件、函式引數、函式返回型別、成員函式本體。
  • 編譯器強制實施 bitwise constness,但編寫程式時應該使用“概念上的常量性” (conceptual constness)。
  • 當 const 和 non-const 成員函式有著實質等價的實現時,令 non-const 版本呼叫 const 版本可避免程式碼重複。

條款04:確定物件被使用前已被初始化

建構函式

對於內建型別,需要自己手動完成初始化
對於內建型別以外的,初始化責任落在建構函式中
值得注意的是,初始化時使用列表初始化替換賦值操作可以提高效率
因為在大括號內賦值的版本,非內建型別會首先呼叫default建構函式再賦值(內建型別賦值和初始化代價相同)
而列表初始化則避免了default初始化(如果成員變數在"成員初始列"中未被指定初值,則會自動呼叫default)

ABEntry::ABEntry()
    :theName(),         //呼叫theName的default建構函式
    theAddress(),
    thePhones(),
    numTimesConsulted(0)//內建型別,必須顯式初始化
    {}

即使成員變數是內建型別,有時也一定要使用初值列,當成員變數是const或者reference,它們一定需要初值,不能被賦值

最簡單的方法就是總是使用成員初始列


成員初始化次序

C++有著固定的"成員初始化次序",即使成員在成員初始列中的出現順序可以有很多種。
在成員初始列中條列各個成員時,為了避免錯誤,最好總以宣告次序為次序。


不同編譯單元內"non-local static物件"的初始化次序

問題是:如果某編譯單元內的non-local static物件的初始化動作使用了另一編譯單元內的某個non-local static物件,它所用到的這個物件可能還未被初始化,因為C++中對於"定義於不同編譯單元內的non-local static物件"的初始化次序並無明確定義。
解決方案:將每個non-local static物件搬到自己的專屬函式中(該物件在函式內被宣告為static),這些函式返回一個reference指向它所含的物件。然後使用者呼叫這些函式,而不直接指涉這些物件。
這個手法的基礎在於: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;
}

這樣的方式仍在多執行緒中帶有不確定性,任何一種non-const static物件,不論local或是non-local,在多執行緒環境下"等待某事發生"都會有麻煩。
處理這個麻煩的一種做法是:在程式的單執行緒啟動階段手動呼叫所有的reference-retruning函式,這可消除與初始化有關的"競速形勢"。

請記住
  • 為內建型物件進行手工初始化,因為 C++ 不保證初始化它們。
  • 建構函式最好使用成員初值列 (member initialization list),而不要在建構函式本體內使用賦值操作。初值列列出的成員變數,其排列次序應該和他們在 class 中宣告次序相同。
  • 為免除“跨編譯單元之初始化次序”問題,請以 local static 物件替換 non-local static 物件。