Make sure that objects are initialized before they're used.
昨天就已經把第四條款看完了,初始化這篇內容非常非常豐富,講解了很多,也讓我的一些疑惑解開了。由於經常加班,寫作時間比較少,我今天看寫完這篇文章,還要看下一條目,所以,我今天不能逐句的進行瞭解析,只能我覺得我認為比較重要的幾點點出來。也希望大家給點指正。
變數的初始化
關於"將對象初始化"這事,C++ 似乎反覆無常。如果你這麼寫:
int i;
他會輸出什麼呢?給大家一段代碼
// Initia_four.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;int _tmain(int argc, _TCHAR* argv[]){ int i; cout<<i<<endl; system("pause"); return 0;}
大家看到了吧,列印的是這種東西,所以,你一定要記住,對變數一定要初始化,當你忘記初始化然後又使用了它,就會有意想不到的災難產生。
建構函式初始化
對於內建類型以外的其它對象,通常初始化責任落在建構函式身上。規則很簡單:確保每一個建構函式都將對象的每一個成員初始化。但是必須清楚一個概念,就是賦值並非初始化。看下面的代碼,假設有類型Person,它的建構函式所做的事情是賦值。雖然Person對象做到了你所期望的結果,但這不是C++中的最佳做法。
class Person {public:Person(string n, unsigned int a) {name = n;age = a;}private:string name;unsigned int age;};
初始化列表的效能要高於賦值操作,因為對於string類型的name,它必須先調用string的default建構函式,然後再賦予新的值(copy assignment),這樣default建構函式所做的事情全部浪費。不過對於內放類型來說,賦值與初始化列表的效能差異幾乎沒有。
所以,建構函式初始化最好用以下方式進行:
class Person {public:Person(string n, unsigned int a) :name(n),age(a){}private:string name;unsigned int age;};
這樣就很高效率了。
許多classes擁有多個建構函式,每個建構函式有自己的成員初值列。如果這種classes存在許多成員變數和/或base classes,多份成員初值列的存在就會導致不受歡迎的重複(在初值列內)和無聊的工作(對程式員而言)。這種情況下可以合理地在初值列中遺漏那些"賦值表現像初始化一樣好"的成員變數,改用它們的賦值操作,並將那些賦值操作移往某個函數(通常是private),供所有建構函式調用。這種做法在"成員變數的初值系由檔案或資料庫讀入"時特別有用。然而,比起經由賦值操作完成的"偽初始化"(pseudo-initialization),通過成員初值列(member
initialization list)完成的"真正初始化"通常更加可取。還有重要的一點就是:如果成員變數是const或者refrences,他們就一定需要初值,不能被賦值。
必需要提到的是初始化列表的初始順序不是根據在建構函式中寫的順序初始化,而是根據成員變數的聲明順序進行構造如下程式編譯是不成功的
// Initia_four.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;template<typename T>class Array {public: Array(size_t size) : _size(size) { _arr = new T[size]; } ~Array() { delete[] _arr; }private: size_t _size; T *_arr;};template<typename T>class SafeArray {public: SafeArray() : _size(5), _arr(_size) { }private: Array<T> _arr; size_t _size;};int _tmain(int argc, _TCHAR* argv[]) { SafeArray<int> sArr; system("pause"); return 0;}
Singleton——單例模式
所謂static對象,其壽命從被構造出來直到程式結束為止,因此stack和heap-based對象都被排除。這種對象包括global對象、定義於namespace範圍內的對象、在classes內、在函數內、以及在file範圍內被聲明為static的對象。函數內的static對象稱為local static對象(因為它們對函數而言是local),其他static對象稱為non-local static對象。程式結束時static對象會被自動銷毀,也就是它們的解構函式會在main()結束時被自動調用。
假設出現就是static變數,這些static變數分別定義在不同的檔案內。最後編譯器怎麼來根據優先順序編譯呢。而如果出現某個變數的初始化依賴與另一個檔案內的變數。為瞭解決這樣的問題,最簡單的方式就是使用Singleton(單例模式),使non-local static成為local static
如下代碼實現了單例模式:
// Initia_four.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;class FileSystem{};//同前FileSystem &tfs()//這個函數用來替換tfs對象;他在FileSystem中可能是個static。{ static FileSystem fs; return fs;}class Directory{};Directory::Directory(){ std::size_t disks = tfs().numDisks();}Directory &tempDir(){ static Directory td; return td;}int _tmain(int argc, _TCHAR* argv[]) { return 0;}
但是從另一個角度看,這些函數"內含static對象"的事實使它們在多線程系統中帶有不確定性。再說一次,任何一種non-const static 對象,不論它是local或non-local,在多線程環境下"等待某事發生"都會有麻煩。處理這個麻煩的一種做法是:在程式的單線程啟動階段(single-threaded startup portion)手工調用所有reference-returning函數,這可消除與初始化有關的"競速形勢(race conditions)"。
請記住
- 為內建型對象進行手工初始化,因為C++不保證初始化它們。
- 建構函式最好使用成員初值列(member initialization list),而不要在建構函式本體內使用賦值操作(assignment)。初值列列出的成員變數,其排列次序應該和它們在class中的聲明次序相同。
- 為免除"跨編譯單元之初始化次序"問題,請以local static對象替換non-local static對象。