Effective C++ (1): Accustoming Yourself to C++

1. Accustoming Yourself to C++

Introduction

本章是最基礎的一點東西, 4條規則.

Rule 01: View C++ as a federation of languages

將 c++ 視作一個語言聯邦而不是一個單一的語言, 才能更好地理解它的特性. 可以認爲 c++ 實際上是由四個子語言構成的:

  • C. 說到底最基本的語句還是以c爲基礎的
  • Object-Oriented C++. 這給 C++ 提供了三大特性, encapsulation, inheritance, polymorphism.
  • Template C++. 這是 c++ 泛型編程(generic programming)部分, 也是大多數程序員最爲稀缺的一部分
  • STL. STL 是個 template 程序庫, 對 容器(container), 迭代器(iterators), 算法(algorithm) 及函數對象(function objects) 有極佳的配合和協調

對於這四個次語言, 有時候高效法則會在四個次語言中規則發生變化. 例如對於 內置類型(也就是C-like)而言, pass-by-value 通常比 pass-by-reference 高效, 當你從 C part of C++ 轉換到 Object-Oriented C++ 的時候, 由於 user-defined 構造函數和析構函數的存在, pass-by-reference-to-const 往往更好.
Remeber:
C++ 高效編程守則視狀況而變化, 取決於你使用C++的哪一個部分.

Rule 02: Prefer consts, enums, and inlines to #defines

使用 consts 來替換 define, 能夠方便調試, 並且優秀的編譯器也不會造成額外的內存開銷.
對於 “in class 常量值” 設定, 通常編譯器不同寫法也不同. 一般這樣寫:

class GamePlayer{
private:
    static const int NumTurns = 5; // 聲明式, 在頭文件中
    int scores[NumTurns];
}
const int GamePlayer::NumTurns; // 定義式, 在實現文件中

如果編譯器(錯誤地)不允許”static 整數型 clas 常量” 完成 “in class 初值設定”, 可以改用所謂的 “the enum hack” 補償做法.

class GamePlayer{
private:
    enum {NumTurns = 5};
    int scores[NumTurns];
}

對於 #define 函數而言, 不是type safety的, 所以建議換成類型安全的 template inline 函數:

template<typename T>
inline void callWithMax(const T& a, const T& b)
{
    f(a > b ? a: b);
}

Remeber:

  • 對於單純常量, 最好以 const 或者 enums 替換 #defines
  • 對於函數的宏, 以 inline 替換 #defines

Rule 03: Use const whenever possible

首先, 區分 const-pointer 和 const-data:

char greeting[] = "Hello";
char *p = greeting;
const char *p = greeting;   // const-data
char const *p = greeting;   // same const-data
char* const p = greeting;   // const-pointer
const char* const p = greeting;     // const-pointer, const-data

對於迭代器而言, const iterator == const pointer, const_iterator == const data.

std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();
*iter = 10; //正確
++iter;     //錯誤, const-pointer
std::vector<int>::const_iterator cIter = vec.begin();
*iIter = 10;    // 錯誤, const-data
++cIter;    //正確

有趣的是, 聲明返回值爲 const 有奇效, 可以防止程序員意外將 ‘==’ 鍵入乘 ‘=’, 但是並不會影響正常使用 :

class Rational{...}
const Rational operator* (const Rational& lhs, const Rational& rhs);
if (a * b = c) ...  // 將報錯
Rational x = a * b;  // 正常

聲明 const 函數, 則函數內不能修改任何成員變量, mutable變量除外. 有時候我們需要對 const 對象進行不同的處理, 例如下面這段代碼, 對於const 對象的 [] 我們希望返回個const對象避免對象被修改. 而非const 對象則不需要, 可以這樣寫:

class TextBlock{
public:
    const char& operator[](std::size_t position) const
    {
        ...
        return text[position];
    }
    char& operator[](std::size_t position) const
    {
        ...
        return 
            const_cast<char&>(
                static_cast<const TextBlock&>(*this)
                    [position]
            );
    }

通過 const_cast, 和 static_cast 來類型強制轉化複用代碼

Remeber:

  • 儘可能使用const來幫助編譯器偵測出錯誤. const可以被用在對象, 參數, 返回類型, 成員函數本身.
  • const和non-const成員函數有着實質等價的實現的時候, 令non-const版本調用const對象可避免代碼重複.

Rule 04: Make sure that objects are initialized before they’re used

要區分初始化和賦值操作, 初始化操作是在構造函數調用之前執行的. 所以可能的話, 儘量把所有成員變量初始化操作放在 member initialization list 中, 效率更高, 對於const 成員變量則必須放到 member initialization list 中, initiate list 會調用成員變量的 copy 構造函數.

另外對於”跨編譯單元內使用 non-local static 對象”時候, 請採取將 local static 對象替換 non-local static 對象的方式, 來保證初始化次序.(類似於單例的方法)

class FileSystem { ... };
FileSystem& tfs(){
    static FileSystem fs;
    return fs;
}
class Directory { ... };
Directory::Directory( params )
{
    ...
    std::size_t disks = tfs().numDisks();
    ...
}

Remeber:

  • 構造函數最好使用成員初值列( member initialization list ), 而不要在構造函數中採用賦值操作
  • 爲免除 ” 跨編譯單元之初始化次序 ” 問題, 請以 local static 對象替換 non-local static 對象
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章