1,視C++爲一種語言聯邦,大體分爲4個部分:ios
A)C。說到底C++還是以C爲基礎。區塊、語句、預處理器、內置數據類型、數組、指針等等通通來自C。算法
B)Object-Oriented C++。這部分也就是C with Classes所訴求的:classes(包括構造函數和虛構函數)、封裝、繼承、多態,虛函數等等。編程
C)Template C++。這是C++的範型編程部分,tamplates威力強大,它給咱們帶來了嶄新的編程範型,也就是所謂的TMP模板元編程。數組
D)STL。STL是個template程序庫,它對容器、迭代器、算法以及函數對象的規範有極佳的緊密配合與協調,而後template及程序庫也能夠其餘想法建置出來。安全
2,儘可能使用const,enum,inline代替#define函數
A)#define不被視爲語言的一部分,屬於預處理指令,編譯不會計入符號表沒法調試。學習
B)#define在預處理器處理階段只作簡單的替換,這將帶來不少預期意外的行爲。如this
#define MAX(a, b) ((a)>(b)?(a):(b))
儘管上述宏定義已將變量用括號括起來了,可是仍是不能避免MAX(++a, b+10)這樣給a所帶來的兩次不是預期內的自增行爲。覺得替換爲:spa
template<typename T> inline T Max(const T& a, const T& b) { return a > b ? a : b; }
3,儘量使用const設計
A)修飾指針
char* p = 「hello」 //non-const pointer, non-const data const char* p = 「hello」 //non-const pointer, const data char* const p = 「hello」 //const pointer,non-const data const char* const p = 「hello’ //const pointer, const data
B)修飾迭代器
const std::vector<int>::iterator it = vec.begin(); //const pointer,non-const data *it = 10 ; //no problem it ++; //error std ::vector<int>::const_iterator it = vec.begin() //non-pointer, const data *it = 10; // error it ++; //no problem
C)修飾函數參數、返回值、成員函數
class TextBlock { public: const char& operator[](std::size_t pos) const { return text[pos]; } char & operator[](std::size_t pos) { return const_cast<char&>(static_cast<const TextBlock&>(*this)[pos]); } private: char text[32]; };
4,肯定對象被使用前已被初始化
A)區分變量初始化和變量賦值二者之間的區別
B)警戒在C++中類未初始化完成以前就使用的問題,由於沒法肯定類與類之間的初始化順序
5,瞭解C++默默編寫並調用那些函數
編譯器能夠暗自爲class建立default構造函數,copy構造函數,copy assignment賦值操做符,以及析構函數.
6,若不想使用編譯器自動生成的函數,就該明確拒絕
將copy構造函數和賦值操做符聲明爲private成員函數且不去實現它們.
7,爲多態基類聲明virtual析構函數
delete一個具備多態性質的基類指針是未定義的行爲,這將致使派生類的析構函數沒法正常調用.由於爲具備多態性質的基類定義virtual析構函數.
8,別讓異常逃離析構函數
在析構函數中發生的異常不容許擴散出去,應該捕獲異常,並選擇終止或吞下該異常.
9,毫不在構造或者析構函數中調用virtual函數
這絕對視一種詭異的行爲...
10,令operator=返回一個reference to *this
這是一個實現的協議,爲了兼容連鎖賦值操做,就像這樣:
Wiget& operator=(const Wiget& rhs) { //do something return *this; }
11,在operator=中處理」自我賦值」
潛在的自我賦值必然存在,並且未必能立馬識別出來.既然實現了operator=就必然要考慮到自我賦值的狀況,參見10點
Wiget& operator=(const Wiget& rhs) { if (this == &rhs) return *this; //do something return *this; }
12,複製對象時視勿忘其每個成分
新增成員後忘記對拷貝構造函數和複製操做函數進行同步修改;
若是基類實現了複製操做函數,在派生類的複製操做函數應顯示調用,以保證基類也被正確的複製.
13,以對象管理資源
A)爲了防止內存泄露,請使用RAII對象,在構造函數中獲取資源,在析構函數中釋放資源,用棧中的局部變量管理堆內存。
B)std::auto_ptr只保存一份指針對象,賦值語句的右值將被置爲NULL。而std::tr1::shared_ptr則是引用計數型智能指針,可是隻能針對單個對象使用,對象數組應使用shared_array。
std::auto_ptr<Wiget> ap(new Wiget); std::tr1::shared_ptr<Wiget> sp(new Wiget);
14,在資源管理類中當心copying行爲
用來管理堆內存的RAII對象發生了複製行爲會怎樣?若是RAII對象是淺拷貝,這將簡單的複製指針,當RAII對象析構的時候,指針指向的內存將被重複釋放,若是RAII對象視深拷貝,這複製一份指針指向的內存,雖然正常但不是咱們所要的。避免發生複製行爲:
A)顯試定義拷貝構造函數和賦值操做符函數,但不實現它,禁止複製行爲。
B)使用複製增長引用計數的方式來肯定什麼時候內存應該被釋放
不只能夠管理內存,還能夠管理其它資源,以下:
class Lock { public: explicit Lock(Mutex* pm):mutextPtr(pm, unlock) { lock(mutexPtr.get()); } private: std::tr1::shared_ptr<Mutext> mutexPtr; };
調用:
Mutex m; { Lock ml(&m); //進入臨界區 ..... //不再用擔憂ml被複制了 }
局部變量被自動釋放。shared_ptr自動調用刪除器unlock,解鎖臨界區。
15,在資源管理類中提供對原始資源的訪問
提供get方法,或者重載operator->,operator*,不提倡operator T() const,可能帶來隱式轉換。
16,成對使用new和delete要採用相同的形式
new對應delete,new[]對應delete[]。警戒typedef矇蔽了你的雙眼。如:
typedef int vec[100]; int *p = new vec; delete p; //error
17,以獨立的語句將newd對象置於shared_ptr之中
process(std::tr1::shared_ptr<Wiget> pW(new Wiget), f1());
上述函數參數作了3件事:new Wiget, f1(), 構造shared_ptr;順序不能肯定,假如f1()異常將致使new Wiget丟失。應該將智能指針構造獨立出來:
std::tr1:;shared_ptr<Wiget> pW(new Wiget); process(pW, f1());
18,讓接口容易被正確使用,不易被誤用
A)重載operator*時返回const 對象,禁止被當作左值使用。
B)確保接口能被正確的調用
C)誰使用誰負責的思想在跨DLL時行不通,new/delete成對使用將致使運行時錯誤,應使用shared_ptr提供的刪除器,將內存管理職責收回。
19,設計Class猶如設計Type
A)新type的對象應該如何被建立和銷燬?new/delete,new[]/delete[]
B)對象初始化和對象賦值又怎樣的差異?不要混淆什麼是初始化,什麼是賦值
C)新type的對象被pass by value意味着什麼?對象拷貝
D)什麼是新type的合法值?約束成員的屬性
E)新的type須要配合某個繼承圖系嗎?注意多態應該實現virtual析構函數
F)新type須要什麼樣的轉換?explicit 構造函數不允許隱式轉換,可是數值類型例外
G)什麼樣的操做符和函數對新type而言是合理的?約束行爲屬性
H)什麼樣的標準函數應該駁回?約束class的默認行爲
I)誰該取用新type的成員?類的封裝和抽象
J)什麼視新type的「未聲明接口」?資源管理,效率,安全性定義
K)新的type有多麼通常化?模板化,特化
L)你真的須要一個新的type?一個新的type以上都是要考慮的
20,寧以pass-by-reference-to-const替換pass-by-value
A)前者更加高效,避免了賦值類的開銷,也避免了類被切割問題;
B)除了自定義類(也有例外),內置類型和STL迭代器、函數對象傳值更加穩當。
21,返回對象時,別妄想返回其reference
局部變量和臨時變量不能做爲指針或者引用返回,其內存隨做用域結束而釋放。
22,將成員變量聲明爲private
所謂越是看不見牽扯越少,提供更好的封裝性,數據一致性,彈性。
23,寧以non-member,non-friend替換member函數
24,若全部參數皆需類型轉換,請爲此採用non-member函數
佐證第23條,operator*實現兩個不一樣版本哪一個好?
版本1:
class Rational { public: const Rational operator*(const rational& rhs) const; };
版本2:
class Rational {}; const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs..., rhs...); }
參考代碼:
#include <stdio.h> class Rational { public: Rational(); Rational(int ca = 0, int cb= 0) : a(ca), b(cb) {} void print() { printf("%d, %d\n", a, b); } public: int a; int b; }; const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.a + rhs.a, lhs.b + rhs.b); } int main(int argc, char* argv[]) { Rational r1(1, 2); Rational r2(3, 4); Rational r3 = r1 * r2; Rational r4 = r1 * 2; Rational r5 = 2 * r1; r1.print(); r2.print(); r3.print(); r4.print(); r5.print(); return 0; }
25,考慮寫一個不拋出異常的swap函數
正確的調用std::swap:
using std::swap; swap(obj1, obj2);
26,儘量的延後變量定義式的出現時間
爲了改善程序效率,遵循使用時再定義的原則。
27,盡少作轉型動做
const_cast將對象的常量性移除
static_cast強迫隱式轉換
dynamic_cast執行安全向下轉型,不建議使用一般使用virtual函數實現調用
reinterpret_cast執行低級轉型
28,避免返回handles指向對象內部成分
將成員定義爲private,由提供方法get出來返回成員的引用、指針或迭代器,這是自相矛盾的。
29,爲「異常安全」而努力是值得的
異常安全函數會不泄露任何資源,不允許數據被破壞。
void PrettyMenu::changeBackgroud(std::istream& imgSrc) { lock(&mutex); delete bgImage; bgImage = new Image(imgSrc); //異常發生點 unlock(&mutex); //永遠不會被unlock } void PrettyMenu::changeBackgroud(std::istream& imgSrc) { Lock ml(&mutex) //參看第14點定義 delete bgImage; bgImage = new Image(imgSrc); //異常發生點 //函數返回就會unlock }
30,透過了解inlining的裏裏外外
inline減小了函數調用的開銷,何時申明爲inline函數應該謹慎。
31,將文件間的編譯依存關係降至最低
對於C++類而言,若是它的頭文件變了,那麼全部這個類的對象所在的文件都要重編,但若是它的實現文件(cpp文件)變了,而頭文件沒有變(對外的接口不變),那麼全部這個類的對象所在的文件都不會因之而重編。所以,避免大量依賴性編譯的解決方案就是:在頭文件中用class聲明外來類,用指針或引用代替變量的聲明;在cpp文件中包含外來類的頭文件。
32,肯定你的public繼承塑模出來視is-a關係
33,避免掩蓋繼承而來的名稱
派生類會掩蓋全部基類的同名函數,可以使用using base::func;在派生類中可見。
34,區分接口繼承和實現繼承
pure virtual函數只具體指定接口繼承
impure virtual函數具體指定接口繼承及缺省實現繼承
non-virtual函數具體指定接口繼承以及強制性實現繼承
35,考慮virtual函數之外的其它選擇
A)使用non-virtual interface(NVI)手法
B)將virtual函數替換爲「函數指針成員變量」
C)以tr1::function成員變量替換virtual函數
D)將一個繼承體系內的virtual函數替換爲另外一個繼承體系內的virtual函數
#include <stdio.h> #include <tr1/memory> #include <tr1/functional> class GameCharacter { public: void healthValue() { printf("before %s\n", __FUNCTION__); doHealthValue(); printf("after %s\n", __FUNCTION__); } private: virtual void doHealthValue() { printf("GameCharacter doHealthValue\n"); } }; class Player : public GameCharacter { private: virtual void doHealthValue() { printf("Player doHealthValue\n"); } }; class GameCharacter2; void defaultHealthCalc(const GameCharacter2& gc) { printf("%s\n", __FUNCTION__); } void dogHealthCalc(const GameCharacter2& gc) { printf("%s\n", __FUNCTION__); } class GameCharacter2 { public: typedef void (*HealthCalcFunc)(const GameCharacter2&); explicit GameCharacter2(HealthCalcFunc hcf = defaultHealthCalc) : m_healthFunc(hcf) {} void healthValue() const { m_healthFunc(*this); } private: HealthCalcFunc m_healthFunc; }; class GameCharacter3; void defaultObjFunc(const GameCharacter3& gc) { printf("%s\n", __FUNCTION__); } void dogObjFunc(const GameCharacter3& gc) { printf("%s\n", __FUNCTION__); } class GameCharacter3 { public: //typedef void (*HealthCalcFunc)(const GameCharacter3&); typedef std::tr1::function<void (const GameCharacter3&)> ObjFunc; explicit GameCharacter3(ObjFunc hcf = defaultObjFunc) : m_healthFunc(hcf) {} void healthValue() const { m_healthFunc(*this); } private: ObjFunc m_healthFunc; }; class GameCharacter4; class CHealthCalc { public: virtual int calc(const GameCharacter4& gc) const { printf("CHealthCalc::%s\n", __FUNCTION__); } }; CHealthCalc defaultCHealthCalc; class GameCharacter4 { public: explicit GameCharacter4(CHealthCalc* pChc = &defaultCHealthCalc) : pHealthCalc(pChc) {} int healthValue() const { return pHealthCalc->calc(*this); } private: CHealthCalc* pHealthCalc; }; int main(int argc, char* argv[]) { //NVI手法 GameCharacter* p = new Player; p->healthValue(); delete p; //將virtual函數替換爲「函數指針成員變量」 GameCharacter2 gc1; gc1.healthValue(); GameCharacter2 gc2(dogHealthCalc); gc2.healthValue(); //以tr1::function成員變量替換virtual函數 GameCharacter3 go1; go1.healthValue(); GameCharacter3 go2(dogObjFunc); go2.healthValue(); //將一個繼承體系內的virtual函數替換爲另外一個繼承體系內的virtual函數 GameCharacter4 gcl1; gcl1.healthValue(); return 0; }
36,毫不從新定義繼承而來的non-virtual函數
37,絕對不要從新定義繼承而來的參數值
39,明智而審慎的使用private繼承
40,明智而審慎的時候多重繼承
41,瞭解隱式接口和編譯器多態
面向對象編程世界老是以顯式接口和運行期多態來解決問題,而模板元編程這相反。
發生在編譯期間的template具現化成爲編譯期多態。
class和template都支持接口和多態。
對class而言接口是顯示的,以函數簽名爲中心,多態則是經過virtual函數發生在運行期。
對template參數而言,接口是隱式的,奠定於有效表達式,多態則是經過template具現化和函數重載解析發生於編譯期。
42,瞭解typename的雙重意義
聲明template參數時,前綴關鍵字class和typename是徹底同樣的。
請使用typename標識嵌套從屬類型名稱,但不得在基類列或成員初始列內使用。
43,學習處理模板化基類內的名稱
模板特化即針對某種類型的進行特殊處理,再也不經過通用模板編譯代碼。
派生類模板調用基類模板的成員函數時應告訴編譯期怎樣調用,可經過this或using 指明調用基類函數
44,將與參數無關的代碼抽離
這些代碼將致使template具現化所帶來的代碼膨脹
45,運用成員函數模板接受全部兼容類型
如何在模板內定義成員函數模板,在定義了泛化構造和賦值操做函數以後,仍應顯示定義通常式。
46,須要類型轉換時請爲模板定義非成員函數
請參看第24點
#include <iostream> template<typename T> class Rational; template<typename T> const Rational<T> doMultiply(const Rational<T>& , const Rational<T>&); template<typename T> class Rational { public: Rational<T>(T ca, T cb) : a(ca), b(cb) {} void print() { std::cout << a << "," << b << std::endl; } friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) { return doMultiply(lhs, rhs); } public: T a; T b; }; template<typename T> const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs) { return Rational<T>(lhs.a + lhs.a, lhs.b + rhs.b); } int main(int argc, char* argv[]) { Rational<int> r1(1, 2); Rational<int> r2(3, 4); Rational<int> r3 = r1 * r2; Rational<double> r4(10.0, 20.0); Rational<double> r5 = r4 * r4; r1.print(); r2.print(); r3.print(); r4.print(); r5.print(); return 0; }
47,請使用traits classes變現類型信息
48,認識template模板元編程
#include <stdio.h> template<unsigned n> struct ftor { enum{nValue = n * ftor<n-1>::nValue}; }; template<> struct ftor<0> { enum{nValue = 1}; }; int main(int argc, char* argv[]) { printf("%d\n", ftor<5>::nValue); return 0; }
49,瞭解new-handler的行爲
std::set_new_handler std::get_new_handler
#include <iostream> #include <new> #include <cstdio> #include <cstdlib> namespace std { typedef void (*new_handler)(); new_handler set_new_handler(new_handler p) throw(); }; void OutofMemory() { char szBuff[128] = ""; snprintf(szBuff, sizeof(szBuff), "%s:out of memory!!!", __FUNCTION__); std::cout << szBuff << std::endl; std::abort(); } //RAII class class NewHolder { public: explicit NewHolder(std::new_handler nh) : handler(nh) {} ~NewHolder() { std::set_new_handler(handler); } private: std::new_handler handler; NewHolder(const NewHolder&); NewHolder& operator=(const NewHolder&); }; //New Handler Support class template<typename T> class NewSupport { public: static std::new_handler set_new_handler(std::new_handler p) throw(); static void* operator new(std::size_t size) throw(std::bad_alloc); static void* operator new[](std::size_t size) throw(std::bad_alloc); private: static std::new_handler currentHandler; }; template<typename T> std::new_handler NewSupport<T>::set_new_handler(std::new_handler p) throw() { std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } template<typename T> void* NewSupport<T>::operator new(std::size_t size) throw(std::bad_alloc) { NewHolder h(std::set_new_handler(currentHandler)); return ::operator new(size); } template<typename T> void* NewSupport<T>::operator new[](std::size_t size) throw(std::bad_alloc) { NewHolder h(std::set_new_handler(currentHandler)); return ::operator new [](size); } template<typename T> std::new_handler NewSupport<T>::currentHandler = NULL; class Test : public NewSupport<Test> { public: Test() { //p = new int[10000000000L]; } ~Test() { //delete p; //p = NULL; } void print() { std::cout << "Test Class print()!!!" << std::endl; } private: int* p; }; int main(int argc, char* argv[]) { // 設置全局的handler // std::set_new_handler(OutofMemory); // int* p = new int[10000000000L]; // delete p; // 設置class Test的handler // Test::set_new_handler(OutofMemory); // Test* p = new Test[10000000000L]; // 看看在哪裏handler了 // Test::set_new_handler(OutofMemory); // Test* p = new Test; // p->print(); // delete p; return 0; }
50,定製new和delete
A)爲了效能
B)爲了收集使用上的統計數據
C)爲了檢測運用錯誤
D)爲了收集動態分配內存之使用統計信息
E)爲了增長分配和歸還速度
F)爲了下降缺省內存管理器帶來的空間額外開銷
G)爲了彌補缺省分配器中的非最佳位對齊
H)爲了將對象成簇集中
51,編寫new和delete時需固守常規
52,寫了placement new也要寫placement delete
定製版的new/delete要對應,同時主要不要掩蓋正常版本
53,不要忽略編譯器警告
嚴肅對待編譯期發出來的抱怨,也不能過度依賴編譯,每一個編譯器都有不一樣。
54,讓本身熟悉包括TR1在內的標準程序庫
55,讓本身熟悉boost