1. 程式人生 > >《Effective Modern C++》讀書筆記

《Effective Modern C++》讀書筆記

Note:為避免各種侵權問題,本文並沒有複製原書任意文字(程式碼除外,作者已經宣告程式碼可以被使用)。需要原書完整中文翻譯的讀者請等待官方譯本的釋出。

正文

為了讓本文更加清晰,依然還是用條款的形式來介紹知識點。(但不能保證我寫的條款就是原書的條款)

條款7:考慮用新的變數初始化語法{}代替舊的()吧

優點:

  • 用{}來初始化變數,可以避免程式設計師不期望的隱式型別轉換(更具體地說應該是narrowing conversions,收縮轉換);
  • 用{}替代(),可以避免A a()被編譯器解析(parsed)成函式宣告的問題;

缺點:

  • 和auto結合得不友好,auto遇到{},auto推導成了std::initializer_list,這不是所期望的;
  • 當類的多個建構函式裡,有一個是用std::initializer_list時,要注意其他建構函式不能用{}語法;
  • 當類有型別轉換函式時,第二個缺點會變得更嚴重:複製建構函式可能不會被呼叫;
  • 當存在std::initializer_list建構函式時,即使構造程式碼不正確,編譯器也不會轉而使用其他建構函式來構造(即使其他建構函式更加match),而是報錯。(一種例外情況是當{...}裡的元素不能被轉換成std::initializer_list的T時,編譯器才會轉而使用其他建構函式);

編寫類建構函式的最佳實踐

當你要給自定義的類加上std::initializer_list建構函式時,要細心考慮這個類被使用時,用{}和()是否一致,是否會有反直覺的結果。也就是說,為了避免上面所說的缺點,為了不坑自己或你的程式碼的使用者,你需要在編寫一個class時保持警惕。如無必要,應儘可能不新增td::initializer_list建構函式。

c++11創造了2個陣營

用()構造亦或用{}構造物件。只使用()的話,是傳統派;只使用{}的話,是革新派。革新派追求{}的那2個優點,對{}的缺點保持樂觀面對的態度;傳統派更重視避免std::initializer_list建構函式帶來的問題。選擇哪一個陣營,看自己喜好了。

對於庫的編寫者,並不是立場的問題

編寫template function,可能會需要構造區域性變數,當局部變數的型別未知時,怎麼知道要用{}還是()?萬一T是一個革新派寫的class,而你又用了{}來構造物件,那麼你的template function的執行情況,可能和T的建構函式過載情況大有關係。也即是說,你的template function是不穩定的。究竟在template function裡用{}還是(),是一個複雜的問題。

條款8:不用考慮了,就用nullptr代替0和NULL

nullptr的好處在哪,請閱讀書中的例子。這個條款十分簡單:不要再用0和NULL來表示空指標,而是用c++11的nullptr,只有好處,沒有壞處。

條款9:用using代替typedef

同條款8,大部分情況下都可以用using代替typedef。

條款10:具有作用域的enum

寫法如下:

enum class Color { black, white, red }; // black, white, red
// are scoped to Color
auto white = false; // fine, no othe
Color c = white; // error! no enumerator named
// "white" is in this scope
Color c = Color::white; // fine
auto c = Color::white; // also fine (and in accord
// with Item 5's advice)

我覺得主要好處是避免名空間汙染。

c++11還允許給enum指定underlying type:

enum class Status: std::uint32_t; // underlying type for
// Status is std::uint32_t
// (from <cstdint>)

意思是,Status的每一個元素都是std::uint32_t型別。預設型別是int。

還有就是,c++11 enum支援前置宣告(類似class的前置宣告)。

條款11:新功能:在成員函式聲明後面加 = delete

這樣子寫:

class A {
public:
    A(const A& ) = delete;
    A& operator=(const A&) = delete;
};

想比c++98的做法(把函式宣告為private,並不定義實現):

class A {
private:
    A(const A& );
    A& operator=(const A&);
};

用 = delete會更好,因為被宣告 = delete的函式,編譯器保證什麼程式碼都不能呼叫它們(會編譯報錯),如果是c++98,有可能是連結時才報錯。

注意到 = delete宣告的函式,是public的,其實是為了讓報錯內容更準確。想一下,如果 = delete的函式是private,然後這個函式被外部呼叫,編譯器可能只是給出"不能呼叫private函式"的錯誤資訊。這可能會誤導呼叫者。

= delete的另一個特性是,它並不是只能用在類成員函式,而是任意函式。看這段程式碼:

bool isLucky(int number); // original function
bool isLucky(char) = delete; // reject chars
bool isLucky(bool) = delete; // reject bools
bool isLucky(double) = delete; // reject doubles and floats

if (isLucky('a'))  // error! call to deleted function
if (isLucky(true))  // error!
if (isLucky(3.5f))  // error!

把後3個函式過載給delete掉,保證了那3種呼叫方式不能被編譯!也就是說,=delete可以用來阻止隱式轉換陷阱。

用= delete還有一個高階的好處:在class內部的函式模板,它的訪問型別只能是public、protected、private其中一種,不能又有public又有private的例項化,所以c++98的"delete"方案對函式模板沒轍。還好,C++11解決了這個問題,用= delete即可:

class A {
public:
    template<typename T>
    void foo(T* ptr) { }

};

template<>
void A::foo<void>(void*) = delete; // still public, but deleted

再引用下作者的一段話:

the C++98 approach is not as good as the real thing. It doesn’t work outside classes, it doesn’t always work inside classes, and when it does work, it may not work unti