條款1:視C++為一個語言聯邦:C、Object-Oriented C++、Template C++、STLC++高效編程守則視情況而變化,取決於使用C++的哪一個部分 條款2:儘量以const,enum,inline替換#define1、對於單純的常量,最好以const對象或enums替代#def ...
條款1:視C++為一個語言聯邦:C、Object-Oriented C++、Template C++、STL
C++高效編程守則視情況而變化,取決於使用C++的哪一個部分
條款2:儘量以const,enum,inline替換#define
1、對於單純的常量,最好以const對象或enums替代#define
2、對於形似函數的巨集,最好用inline函數替代#define
條款3:儘可能使用const
3、儘量使用const,將某些東西聲明為const可以幫助編譯器偵測出錯誤的用法。const可被施加與任何作用域內的對象、函數參數、函數返回類型、成員函數本體。
4、編譯器強制實施bitwise constness, 但編寫程式時應該使用“概念上的常量性”
5、當const和non-const成員函數有著實質等價的實現時,令non-const版本調用const版本可避免代碼重覆。
條款4:確定對象使用前已被初始化。
6、對內置類的對象進行手工初始化,因為C++不保證初始化它們。
7、構造函數最好使用成員初值列,而不要在構造函數內使用賦值操作。初值列列出的成員變數,其排列次序應該和它們在class中聲明的次序相同。
8、為免除“跨編譯單元之初始化次序”問題,請以local static對象替換non-local static對象。
條款5:瞭解C++默默編寫並調用哪些函數。
9、編譯器可以暗自為class創建default構造函數、copy構造函數、copy assignment操作符,以及析構函數。
10、當手工創建一個構造函數後,編譯不會再創建default構造函數。
11、內含引用或const成員,編譯器不會創建copy assignment操作符。
條款6:若不想使用編譯器自動生成的函數,應該明確拒絕。
12、為駁回編譯器自動提供的技能,可將相應的成員函數聲明為private並且不予實現。
條款7:為多態基類聲明virtual析構函數。
13、polymorphic帶有多態性質的base classes 應該聲明一個virtual析構函數。如果class帶有任何virtual函數,它就應該擁有一個virtual析構函數。但子類以父類指針的形式被析構時,子類對象的成分沒有被銷毀,故需要將父類的析構函數需要為virtual
14、Classes的設計目的如果不是作為base classes使用,或不是為了具備多態性,就不應該聲明virtual析構函數。
15、若父類帶有pure virtual(純虛函數)時,子類的析構函數中會有對父類析構函數的調用,因此必須為父類的純虛函數提供一份定義。
條款8:別讓異常逃離析構函數。
16、析構函數絕對不要吐出異常,如果一個被析構函數調用的函數可能拋出異常,析構函數應該捕捉任何異常,然後吞下它們(不再傳播)或結束程式。
17、如果客戶需要對某些操作函數運行期間拋出的異常做出反應,那麼class應該提供一個普通函數(而非析構函數)執行該操作。
條款9:絕不在構造和析構過程中調用virtual函數。
18、子類構造時,先調用base class,此時子類被認為是一個base class。析構時先調用自身的析構函數,再調用base class的析構函數。
條款10:令operator= 返回一個reference to *this。
19、
Widget& operator=(const Widget& rhs) { ... return* this; }
條款11:在operator=中處理“自我賦值”
20、確保當對象自我賦值時operator=有良好的行為。其中技術包括比較“來源對象”和“目標對象”的地址、精心周到的語句順序、以及copy-and-swap。
21、確定任何函數如果操作一個以上的對象,而其中多個對象是同一個對象時,其行為仍然正確。
條款12:複製對象時勿忘其每一成分。
22、(1)複製所有local成員變數,(2)調用所有base classes內適當的copying函數。
23、copying函數應該確保複製“對象內的所有成員變數”及“所有base class成分”。
24、不要嘗試以某個copying函數實現另一個copying函數。應將共同機能放進第三個函數中,並有兩個copying函數共同調用。
class Customer { public; ... Customer(const Customer& rhs); Customer& operator=(const Customer& rhs); private: std::string name; }; Customer::Customer(const Customer& rhs); { ... } Customer& Customer::operator=(const Customer& rhs) { ... name = rhs.name; return *this; } class PriorityCustomer:public Customer { public: PriorityCustomer(const PriotyCustomer& rhs); priorityCustomer& operator=(const PriotyCustomer& rhs); private: int priority; }; PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs), priority(rhs.priority) { ... } PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs) { ... Customer::operator=(rhs); priority = rhs.priority; return *this; }
條款13:以對象管理資源。
25、以對象管理資源的兩個關鍵想法:
(1)、獲得資源後立刻放進管理對象。資源獲取時機便是初始化時機。
(2)、管理對象運用析構函數確保資源被釋放。
26、使用shared_ptr智能指針。
條款14:在資源管理類中小心copying行為。
27、複製RAII對象必須一併複製它所管理的資源,所以資源的copying行為決定RAII對象的copying行為。
28、普遍而常見的RAII class copying行為是:抑制copying、施行引用計數法。不過其他行為也都可能被實習。
條款15、在資源管理類中提供對原始資源的訪問。
29、APIs往往要求訪問原始資源,所以每一個RAII class應該提供一個“取得其所管理資源”的方法。
30、對原始資源的訪問可能經由顯示轉換或隱式轉換。一般而言顯示轉換更安全,隱式轉換比較方便。
條款16、成對使用new和delete時要採取相同形式。
31、如果在new表達式中使用了[],則在對應delete中也必須使用[]。若在new表達式中沒有使用[],則在delete表達式中也不能使用[]。
條款17:以獨立語句將newed對象置入智能指針。
32、以獨立語句將newed對象置入智能指針,否則,一旦有異常拋出,就可以造成難以察覺的記憶體泄漏。
條款18:讓介面容易被正確使用,不易被誤用。
33、好的介面容易被正確使用,不容易被誤用。
34、“促進正確使用”的方法包括介面的一致性,以及與內置類型的行為相容。
35、“阻止誤用”的方法包括建立新類型、限制類型上的操作,束縛對象值,以及消除客戶的資源管理責任。
36、tr1::shared_ptr支持定製型刪除器。可以方法DLL問題,可被用來自動解除互斥鎖等。
std::tr1::shared_ptr<className> ptr(static_cast<calsssName*>(0), deletebyself);//deletebyself為自定義的刪除器。
條款19、設計class猶如設計type。
37、設計一個class需要考慮一下:
1、新type的對象應如何被創建和銷毀?
2、對象的初始化和對象的賦值該有什麼樣的差別?
3、新type對象如果被passed by value,意味著什麼?copy構造函數用來定義一個type by value該如何實現。
4、什麼是新type的合法值?
5、新的type需要配合某些繼承圖系嗎?
6、新type需要什麼樣的轉換?
7、什麼樣的操作符和函數對新type時合理的?
8、什麼樣的函數應該駁回?那些正式必須聲明為private者。
9、誰該取用新type的成員?
10、什麼時新type的“未聲明介面”?
11、新type有多麼一般化?
12、真的需要一個新type嗎?
條款20、寧以pass-by-reference-to-const 替換 pass-by-value
38、儘量以pass-by-reference-to-const 替代 pass-by-value。前者比較高效,並可避免切割問題。
39、以上規則並不適用於內置類型、以及STL的迭代器和函數對象。對它們而言,pass-by-value往往比較適當。
條款21、必須返回對象時,別妄想返回其reference。
40、一個“必須返回新對象”的函數的正確寫法是:就讓那個函數返回一個新對象。
41、絕不要返回pointer或reference指向一個local stack對象,或返回refercence指向一個heap-allcoated對象,或返回pointer或reference指向local static對象而言有可能同時需要多個這樣的對象。
條款22、將成員變數聲明為private。
42、切記將成員變數聲明為private,可賦予客戶訪問數據的一致性、可細微劃分訪問控制、允許約束條件得以保證,並提供class作者以充分的實現彈性。
43、protected並不比public更具封裝性。
條款23、寧以non-member, non-friend替換member函數。
44、以non-member,non-friend函數替換member函數,這樣可以增加封裝性、包裹彈性和機能擴充型。
條款24、若所有參數皆需類型轉換,請為此採用non-member函數。
45、如果需要為某個函數的所有參數(包括被this指針所指的那個隱喻參數)進行類型轉換,那麼這個函數必須是個non-member。
條款25、考慮寫出一個不拋異常的swap函數。
46、
class WidgetImp1 { public: private: int a, b, c; std::vector<double> v; } class Widget //使用pimpl手法 { public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs); { *pImpl = *(rhs.pImpl); } class swap(Widget& other) { using std::swap; // swap(pImpl, other.pImpl); // } ... private: WidgetImpl* pImpl; } namespact std{ template<> //表示它是std::的一個全特化版本 void swap<Widget>(Widget& a, Widget& b) //<Widget>表示這一特化版本 { //針對T是Widget而設計 a.swap(b); } };
47、當std::swap對你的類型效率不高時,提供一個swap成員函數,並確定這個函數不拋出異常。
48、如果你提供一個member swap,也該提供一個non-member swap用來調用前者。對於classes,而非templates,也請特化std::swap.
49、調用swap時應針對std::swap使用using聲明式,然後調用swap並且不帶任何命名空間資格修飾。
50、為用戶定義類型進行std templates全特化是好的,但千萬不要嘗試在std內加入某些對std而言全新的東西
條款26、儘可能延後變數定義式的出現時間。
條款27、儘量少做轉型動作。
51、如果可以,儘量避免轉型,特別是在註重效率的代碼中避免dynamic_cast,如果有個設計需要轉型動作,試著發展無需轉型的替代設計。
52、如果轉型是必要的,試著將它隱藏於某個函數背後碼,客戶隨後可以調用該函數,而不需要將轉型放進他們自己的代碼內。
53、寧可使用C++新式轉型,不要使用舊式轉型。前者容易辨識出來,而且比較有著分門別類的職稱。
條款28、避免返回handles指向對象內部成分。
54、避免返回handles(包括references、pointer、迭代器)指向對象內部。遵守這個條款可增加封裝性,幫助const成員函數的行為像個const,並將發生“虛弔號碼牌”的可能性降至最低。
條款29、為“異常安全”而努力是值得的
55、異常安全函數即使發生異常也不會泄漏資源或允許任何數據結構敗壞。這樣的函數區分為三種可能的保證:基本型、強烈型、不拋異常型。
56、“強烈保證”往往能夠以copy-and-swap實現,但並非對所有函數都可實現或具備現實意義。
57、函數提供的“異常安全保證”通常最高只等於其所調用之各個函數的“異常安全保證”中的最弱者。
條款30、透徹瞭解inlining的裡裡外外。
58、將大多數inlining限制在小型、被頻繁調用的函數身上。這可使日後的調試過程和二進位升級更容易,也可使潛在的代碼膨脹問題最小化,使程式的速度提升機會最大化。
59、不要只因為function templates出現在頭文件,就將它們聲明為inline。
條款31、將文件間的編譯依賴關係降至最低
60、如果使用object references或object pointers可以完成任務,就不要使用objects。
如果能夠,儘量以class聲明式替換class定義式
為聲明式和定義式提供不同的頭文件
61、支持“編譯依存性最小化”的一般構想是:相依於聲明式,不要相依於定義式。基於此構想的兩個手段時Handle classes和Interface classes。
1 class Person 2 { 3 public: 4 virtual ~Person(){} 5 virtual string name() const = 0; 6 virtual string birthDate() const = 0; 7 virtual string address() const = 0; 8 9 static shared_ptr<Person> create(const string& name, const string& birthDate, string& address); 11 12 }; 13 14 class RealPerson:public Person 15 { 16 public: 17 RealPerson(const string& name, const string& birthDate, const string& address) 18 :m_name(name), m_birthDate(birthDate), m_address(address) 19 { 20 21 } 22 virtual ~RealPerson() 23 { 24 25 } 26 27 string name() const 28 { 29 return m_name; 30 } 31 32 string birthDate() const 33 { 34 return m_birthDate; 35 } 36 37 string address() const 38 { 39 return m_address; 40 } 41 42 private: 43 string m_name; 44 string m_birthDate; 45 string m_address; 46 }; 47 48 shared_ptr<Person> Person::create(const string& name, const string& birthDate, string& address) 50 { 51 return shared_ptr<Person>(new RealPerson(name, birthDate, address)); 52 } 53 54 void doSomething() 55 { 56 string name = "hei"; 57 string birthDate = "12"; 58 string address = "China"; 59 shared_ptr<Person>ptr(Person::create(name, birthDate, address)); 60 cout << ptr.get()->name().data() << endl; 61 cout << ptr.get()->birthDate().data() << endl; 62 cout << ptr.get()->address().data() << endl; 63 }
62、程式庫頭文件應該以“完全且僅有聲明式”的形式存在。這種做法不論是否設計templates都適用。
條款32、確定你的public繼承塑模出is-a關係
63、“public繼承”意味著is-a。適用於base classes身上的每一件事情一定適用於derived classes身上,因為每一個derived class對象也是一個base class對象。
條款33、避免遮掩繼承而來的名稱
64、derived classes內的名稱會遮掩base classes內的名稱。在public繼承下從來沒人希望如此。
65、為了讓被遮掩的名稱再見天日,可以使用using聲明式或轉交函數。
class Base1 { public: virtual void mf1() = 0; virtual void mf1(int) { cout << "base mf1(int)" << endl; } virtual void mf2() { cout << "base mf2()" << endl; } void mf3() { cout << "base mf3()" << endl; } void mf3(double) { cout << "base mf3(double)" << endl; } }; class Derived1:public Base1 { public: using Base1::mf1; using Base1::mf3; virtual void mf1() { cout << "Derived1 mf1()" << endl; } void mf3() { cout << "Derived1 mf3()" << endl; } }; void doSomething() { Derived1 d1; d1.mf1(); d1.mf2(); d1.mf3(); d1.mf1(2); //不加using Base1::mf1,則錯誤,Derived的mf1(),會遮掩掉父類的mf1(); d1.mf3(2.0); //不見using Base1::mf3,則錯誤 }
條款34、區分介面繼承和實現繼承
66、可以為pure virtual函數提供定義(提供實現代碼),但調用它的唯一途徑是“調用時明確指出class名稱”
67、介面繼承和實現繼承不同。在public繼承之下,derived classes總是繼承base class的介面。
68、pure virtual(純虛函數)只具體指定介面繼承。
69、impure virtual(非純虛函數)具體指定介面繼承及預設實現繼承。
70、non-virtual函數具體指定介面繼承以及強制實現繼承。
條款35、考慮virtual函數外的其他選擇
71、由Non-virtual Interface手法實現Template Method模式。
class GameCharacter { public: int healthValue() const { ... // 事前工作:可鎖定互斥器、製作運轉日誌記錄項、驗證class約 //束條件、驗證函數的先決條件等等 int retVal = doHealthValue(); //真正做些事 ... // 事後工作: return retVal; } private: virtual int doHealthValue() const //derived classes 可重新定義 { ... // 預設演算法 } };
72、由Function Pointers現實Strategy模式
class GameCharacter; int defaultHealthCalc(const GameCharacter& gc); class GameCharacter { public: typedef int (*HealthCalcFunc)(const GameCharacter&); explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc):healthFunc(hcf) { } int healthValue() const { return healthFunc(*this); } private: HealthCalcFunc healthFunc; }; class EvilBadGuy:public GameCharacter { public: explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc):GameCharacter(hcf) { } }; int loseHealthQuickly(const GameCharacter&); int loseHealthSlowly(const GameCharacter&); EvilBadGuy ebg1(loseHealthQuickly); EvilBadGuy ebg2(loseHealthSlowly);
73、由std::function完成Strategy模式。
class GameCharacter; int defaultHealthCalc(cosnt GameCharacter& gc); class GameCharacter { public: typedef std::function<int(const GameCharacter&)> HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc):healthFunc(hcf) { } int healthValue() const { return healthFunc(*this); } private: HealthCalcFunc healthFunc; };
74、古典的Strategy模式。
GameCharacter是某個繼承體系的根類, EvilBadGuy和EyeCandyCharacter都是其derived classes;
HealthCalcFunc是另一個繼承體系的根類,SlowHealthLoser和FaseHealthLoser都是其derived classes;
class GameCharacter;
class HealthClacFunc { public: virtual int calc(const GameCharacter& gc) const { } };
HealthCalcFunc defaultHealthCalc;
class GameCharacter { public: explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc):pHealthCalc(phcf) { } int healthValue() const { return pHealthCalc->calc(*this); } private: HealthCalcFunc* pHealthCalc; };
條款36、絕不重新定義繼承而來的non-virtual函數
條款37、絕不重新定義繼承而來的預設參數值。
75、絕不重新定義一個繼承而來的預設參數值,因為預設參數值都是靜態綁定,而virtual函數唯一應該覆寫的東西卻是動態綁定。
條款38、通過複合塑模出has-a或“根據某物實現出”
76、複合(composition)的意義和public繼承完全不同。
77、在應用域(application domain)複合意味has-a(有一個)。在現實域(implementation domain)複合意味is-impllemented-in-terms-of(根據某物實現出)。
條款39、明智而審慎地使用private繼承。
78、private繼承意味is-implemented-in-terms-of(根據某物實現出)。它通常比複合(composition)的級別低。但當derived class需要訪問protected base class的成員,或需要重新定義繼承而來的virtual函數時,這麼設計是合理的。換句化說,當面對“並不存在is-a關係”的兩個classes,其中一個需要訪問另一個的protected成員,或需要重新定義其一或多個virtual函數,private繼承極有可能成為正確的設計策略。
79、和複合不同,private繼承可能造成empty base最優化。這對致力於“對象尺寸最小化”的程式庫開發者而言可能很重要。
條款40、明智而審慎地使用多重繼承。
80、多重繼承比單一繼承複雜。它可能導致新的歧義性,以及對virtual繼承的需要。
81、virtua繼承會增加大小、速度、初始化複雜度等等成本。如果virtual base classes不帶任何數據,將是最具適用價值的情況。
82、多重繼承的確有正當用途。其中一個情節設計“public繼承某個Interface class”和“private繼承某個協助實現的class”的兩相組合。
條款41、瞭解隱式介面和編譯器多態
83、classes和templates都支持介面(interfaces)和多態(polymorphism)。
84、對classes而言介面是顯式的(explicit),以函數簽名為中心。多態是通過virtual函數發生在運行期。
85、對template參數而言,介面是隱式的,基於有效表達式。多態則通過template具現化和函數重載解析發生於編譯器。
條款42、瞭解typename的雙重意義。
86、聲明template參數時,首碼關鍵字class和typename可互換。
87、請使用關鍵字typename表示嵌套從屬類型名稱;但不得在base class lists(基類列)或member initalization list(成員初值列)內以它作為base class修飾符。
//嵌套從屬類型名稱示例: template<typename C> void print2nd(const C& container) { if(container.size() >= 2) { typename C::const_iterator iter(container.begin());//在預設的情況下C::const_iterator不被認為是類型,在嵌套從屬關係C::const_iterator前加typename說明C::const_iterator為類型(使用vs2015,和最新的g++編譯,不加typename貌似也能正常工作) } }
/*
一般性規則:任何時候想要在template中指涉一個嵌套從屬類型名,就必須在緊鄰它的前一個位置放上typename。 */ //不能使用typename示例 template<typename T> class Derived::public Base<T>::Nested //base class list中不允許typename { public: explicit Derived(int x):Base<T>::Nested(x) //mem.init.list中不允許typename { typename Base<T>::Nested temp; //嵌套從屬名稱,需要加typename; } }; //接手一個迭代器,為迭代器所指的對象做一份local副本temp; template<typename IterT> void workWithIterator(IterT iter) { typename std::iterator_traits<IterT>::value_type temp(*iter) } std::iterator_traits<IterT>::value_type:類型為ItetT對象所指之物的類型
條款43、學習處理模板化基類內的名稱。
88、可以在derived class templates內通過“this->”指涉base class templates內的成員名稱,或由一個明白寫出的“base class資格修飾符完成”。
class CompanyA { public: void sendCleartext(const std::string& msg); void sendEncrypted(const std::string& msg); }; class CompanyB { public: void sendCleartext(const std::string& msg); void sendEncrypted(const std::string& msg); }; class CompanyZ { public: void sendEncrypted(const std::string& msg); }; class MsgInfo { }; template<typename Company> class MsgSender { public: void sendClear(const MsgInfo& info) { std::string msg; //info->msg Company c; c.sendCleartext(msg); } void sendSecret(const MsgInfo& info) { } }; //提供一個針對CompanyZ的MsgSender特化版 template<> class MsgSender<CompanyZ> // { public: void sendSecret(const MsgInfo& info) { } }; template<typename Company> class LoggingMsgSender:public MsgSender<Company> { public: void sendClearMsg(const MsgInfo& info) { //logging begin sendClear(info); //不能通過編譯,編譯器知道base class templates有可能被特化。 //logging end; } }; template<typename Company> class LoggingMsgSender:public MsgSender<Company> { public: void sendClearMsg(const MsgInfo& info) { //logging begin this->sendClear(info); //成立,假設sendClear將被繼承 //logging end; } }; template<typename Company> class LoggingMsgSender:public MsgSender<Company> { public: using MsgSender<Company>::sendClear; //告訴編譯器,請假設sendClear位於base class內 void sendClearMsg(const MsgInfo& info) { //logging begin sendClear(info); //成立 //logging end; } }; template<typename Company> class LoggingMsgSender:public MsgSender<Company> { public: void sendClearMsg(const MsgInfo& info) { //logging begin MsgSender<Company>::sendClear(info); //成立,假設sendClear將被繼承下來 //logging end; } };
條款44、將與參數無關的代碼抽離templates。
89、Templates生產多個classes和多個函數,所以任何template代碼都不該與某個造成膨脹的template參數產生相依關係。
90、因非類型模板而造成的代碼膨脹,往往可以消除,做法是以函數參數或class成員變數替換template參數。
91、因類型參數而造成的代碼膨脹,往往可以降低,做法是讓帶有完全相同二進位表述的具現類型共用現實碼。
條款45、運用成員函數模板接受所有相容類型。
92、使用member function templates生產“可接受所有相容類型”的函數。
93、如果聲明member templates用於“泛化copy構造”或“泛化assignment操作”,還是需要生命正常的copy構造函數和copy assignment操作符。
template<class T> class shared_ptr { public: shared_ptr(shared_ptr const& r); template<class Y> shared_ptr(shared_ptr<Y> const& r); //聲明泛化copy構造函數,並不會阻止編譯器生產預設的copy構造函數。 shared_ptr& operator=(shared_ptr const& r); template<class Y> shared_ptr& operator=(shared_ptr<Y> const& r); };
條款46、需要類型轉換時為模板定義非成員函數
94、當編寫一個class template,而它所提供的與此template相關的函數支持所有參數之隱式類型轉換時,將那些函數定義為class template內部的friend函數。
template<typename T> class Rational { public: friend const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator()); } }; //對於複雜的函數 template<typename T> class Rational; //聲明 template<typename T> const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs); template<typename T> class Rational { public: friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) { return doMultiply(lhs, rhs); } };
條款47、使用traits classes表現類型信息
95、Traits classes 使得類型相關信息在編譯器可用。它們以template和templates特化完成實現。
96、整合重載技術後,traits classes有可能在編譯器對類型執行if...else測試(if...else在運行期檢測)
template<...> class deque { public: class iterator { public: typedef random_access_iterator_tag iterator_category; ... }; };
template<...> class list { public: class iterator { public: typedef bidirectional_iterator_tag iterator_category; ... }; }; //iterator_traits響應iterator class的嵌套式typedef template<typename IterT> struct iterator_traits { typedef typename IterT::iterator_category iterator_category; ... }; //對於指針(也是一種迭代器)需要提供一個偏特化版本 template<typename IterT> struct iterator_traits<IterT*> { typedef random_access_iterator_tag iterator_category; ... }; //利用重載在編譯器檢查類型 template<typename IterT, typename DistT> void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) { iter += d; } template<typename IterT, typename DistT> void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) { if(d >= 0) { while(d--) { ++iter; } } else { while(d++) { --iter; } } } template<typename IterT, typename DistT> void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) { iter += d; } template<typename IterT, typename DistT> void doAdvance(IterT& iter, DistT d, std::inputs_iterator_tag) { if(d < 0) { throw std::out_of_range("Negative distance"); } while(d--) { ++iter; } } //調用“勞工函數” template<typename IterT, typename DistT> void advance(IterT& iter, DistT d) { doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category()); }
條款48、認識template元編程
97、Template metaprogramming(TMP,模板元編程)是編寫template-based C++程式並執行於編譯器的過程。
98、Template metaprogramming(TMP,模板元編程)可以將工作由運行期移往編譯期,因而得以實現早期錯誤偵測和更高的執行效率。
99、TMP可被用來生成“基於政策選擇組合”的客戶定製代碼,也可用來避免生成某些特殊類型並不合適的代碼。
100、TMP並沒有真正的迴圈結構,因此迴圈由遞歸完成,“遞歸模板具現化”
template<unsigned n> struct Factorial { enum {value = n * Factorial<n-1>::value}; };
template<> struct Factorial<0> //特殊情況:Factorial<0>; 結束遞歸的條件 { enum {value = 1}; };
條款49、瞭解new-handler的行為
101、一個良好設計的new-handler函數必須做以下的事情:
讓更多記憶體可被使用
安裝另一個new-handler
卸載new-handler
拋出bad_alloc(或派生自bad_alloc)的異常
不返回,通常調用abort或exit。
//令class提供自己的set_new_handler和operator new //需要聲明一個類型為new_handler的static成員,用以指向class Widget的new-handler。 class Widget { 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); private: static std::new_handler currentHandler;//static成員必須在class定義式以外被定義(除非是const而且是整型) }; std::new_handler Widget::currentHandler = 0;//在class實現文件內初始化為null std::new_handler Widget::set_new_handler(std::new_handler p) throw() { std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } //資源管理類 class NewHandlerHolder { explicit NewHandlerHolder(std::new_handler nh):handler(nh) {} ~NewHandlerHolder() { std::set_new_handler(handler); } private: std::new_handler handler; NewHandlerHolder(const NewHandlerHolder&); //阻止copying NewHandlerHolder& operator=(const NewHandlerHolder&): }; void* Widget::operator new(std::size_t size) throw(bad_alloc) { NewHandlerHolder h(std::set_new_handler(currentHandler));//安裝Widget的new-handler return ::operator new(size); //分配記憶體或拋出異常,恢復global new-handler. } //使用new-handling void outOfMem(); //函數聲明,此函數在Widget對象分配失敗時調用 Widget::set_new_handler(outOfMen); //設定outOfMem為Widget的new-handling函數 Widget* pw1 = new Widget; // 如果記憶體分配失敗,調用outOfMem std::string* ps = new std::string; //若記憶體分配失敗,調用global new-handling函數(若有) Widget::set_new_handler(0); //設定Widget專屬的new-handling函數為null Widget* pw2 = new Widget; //若記憶體分配失敗,立刻拋出異常(class Widget並沒有專屬的new-handling函數)
102、set_new_handler允許客戶指定一個函數,在記憶體分配無法獲得滿足時被調用。
103、Nothrow new是一個頗為局限的工具,因為它只適用於記憶體分配;後繼的構造函數調用還是可能拋出異常。
條款50、瞭解new和delete的合理替換時機
104、有許多理由需要寫個定義的new和delete,包括改善效能、對heap運用錯誤進行調試、收集heap使用信息。
為了檢測運用錯誤
為了收集動態分配記憶體之使用統計信息
為了增加分配和歸還的速度
為了降低預設記憶體管理器帶來的空間額外開銷
為了彌補預設分配器中的非最佳齊位
為了將相關對象成簇集中
為了獲得非傳統的行為(比如自定義delete,在其中所歸還記憶體內容覆蓋為0,增加應用程式的數據安全性)
條款51、編寫new和delete時需要固守常態
105、operator new應該內含一個無窮迴圈,併在其中嘗試分配記憶體,如果它無法滿足記憶體需求,就該調用new-handler。它也應該有能力處理0bytes申請。class專屬版本則還應該處理比正確大小更大(錯誤)的申請。
106、operator delete應該在收到null指針時不做任何事。class專屬版本則還應該處理比正確大小更大的(錯誤)申請。
條款52、寫了placement new也要寫placement delete
107、operator new接受的參數除了一定會有的size_t外還有其它參數,這比那時所謂的placement new。
特別有用的一個“接受一個指針指向對象該被構造之處”
void* operator new(std::size_t, void* pMemory) throw(); //標準庫 #include <new>
108、當寫一個placement operator new,請確定也寫出對應的placement operator delete。如果沒有這樣做,你的程式可能會發生隱微而時斷時續的記憶體泄漏。
109、當聲明placement new 和 placement delete,請確定不要無意識地遮掩它們的正常版本。
//