1. 讓自己習慣C++
1.1 C++ 是一個多重泛型程式設計語言(multiparadigm programming),支援:過程形式(procedural),面向對象形式(object-oriented),函數形式(functional),泛型式(generic),元程式設計(metaprogramming)。
1.2 将C++看做主語言,那麼他就四種次語言組成:C,object-oriented C++,Template C++, STL。
請記住:
C++ 高效程式設計守則視狀況而變化,取決于你使用C++的哪一部分。
2.1 用#define定義常量時,由于#define是preprocessor負責的,編譯器看不到,是以編譯出錯時,會很麻煩。盡量以const取代之。
2.2 常量式通常被放在頭檔案中,以便被不同的源碼引入。
2.3 #define無法定義類的常量成員,更不能提供封裝,const就可以。類中的static const成員,不僅要在類内初始化,還要在類外定義。
//頭檔案
class A{
private:
static const int Num = 5;
int scores[Num];
}
//源檔案
const int A::Num;
2.4 在2.3中提到,類内的static const成員要在類内初始化,如果編譯器不允許類内初始化,可以用enum hack的做法,來變相實作。
enum{ Num = 5 };
2.5 在C++11标準以前,隻允許對類的static const integral成員做in-class initialization。在C++11之後,類的所有資料成員均可做類内初始化。另外,enum hack實際上利用enumeration不是類型安全的特點(弊端),這個特點在C++11中通過scoped enumeration來彌補,是以enumerator到整形的隐式轉換是不提倡做的。故,在C++11下,需要用enum來替換#define的情況幾乎不存在。
2.6當#define定義類似函數的功能時,帶來了很多不安全因素,通常用内聯函數(模闆)代替。
#define MAX(a, b) (a)>(b)?(a):(b)
int a = 5, b = 0;
MAX(++a, b);//a被累加了兩次
1)對于單純常量,最好以const對象或者enums替換掉#defines。
2)對于形似函數的宏(macros),最好用inline函數替換掉#defines。
3.1 在能用const的地方就使用const有利于編譯器對對象進行處理,而且可以避免錯誤的使用。
3.2 作為成員函數,是否有const qualifier差別,可以被重載。
3.3 const成員函數的const的意義,有兩大流行概念:bitwise constness(又稱physical constness)和logical constness。
3.4 使用mutable可以釋放掉non-static成員變量的bitwise constness限制。
3.5 當同時定義一個功能的const版本和nonconst版本時,注意代碼重用:nonconst版本調用const版本,不要用反。
public:
const char& operator[](size_t index) const{
/*...*/
return text[index];
char& operator[](size_t index){
const_cast<char&>( static_cast<const A&>(*this)[index] );
};
1)将某些東西聲明為const可幫助編譯器偵測出錯用法,const被施加于任何作用域内的對象,函數參數,函數傳回類型,成員函數本體。
2)編譯器強制實施bitwise constness,但我們在編寫程式時應該使用logical constness。
3)當const合nonconst成員函數有着實質等價的實作時,令nonconst版本調用const版本可以避免代碼重複。
4.1 C++中default initialization和value initialization的差別。
4.2 類資料成員的初始化是發生在constructor body執行之前的(通過initialization list和in-class initializer),構造函數體内是指派,不是初始化。
4.3 類的資料成員的初始化順序總是固定的,若initialization list和in-class initializer都沒有對某成員執行初始化,那麼該成員執行default initialization。
4.4 注意在用new動态申請時new type()和new type的差別。
4.5 若object的lifetime從構造出來開始,一直到程式結束,那麼就稱之為static對象(未必被static修飾,如全局變量),包括global對象、定義于namespace作用域内的對象,以及在class内、函數内、檔案作用域内的被聲明為static的對象;他們都存放于靜态存儲區(或者稱作全局區)。在函數内的static對象被稱作local static對象(用static修飾),其餘均是非local的。C++對于定義于不同編譯單元的non-local static對象的初始化次序沒有明确規定。當一個non-local static對象的初始化要依賴于另一個編譯單元内的non-local static對象時,往往結果是不可預測的。解決辦法,将非local static對象,放到函數内,即轉為local static。這就是singleton模式的做法。
1)為built-in對象手工初始化,因為C++不保證初始化。
2)構造函數最好使用成員初始化清單(member initialization list),而不要在構造函數體内使用指派操作。初始化清單列出的成員變量,其排列次序應該和它們在類中的聲明次序相同。
3)為免除“跨編譯單元之初始化次序”問題,請以local static對象代替non-local static對象。
2. 構造,析構,指派運算
5.1 編譯器合成的函數,隻有在被需要(被調用)的時候才會被編譯器建立出來。
編譯器可以暗自為class建立default構造函數、copy構造函數、copy assignment操作符,以及析構函數。補充:在C++11标準下,編譯器還會合成move-copy構造函數和move-copy assignment操作符。
6.1 針對将成員函數聲明為private并且不予實作的方法。補充:做了測試,發現不适用于非純虛函數。當非純虛函數沒有定義時,隻要執行構造函數,就會出現連結錯誤。
為了駁回編譯器自動(暗自)提供的機能,可将相應的成員函數聲明成private并且不予實作。使用像Uncopyable這樣的base class也是一種做法。補充:在C++11中可以直接使用=delete限定符來明确駁回。
7.1 由于STL容器的析構函數都不是virtual的,是以我們不應該(不是不能)繼承他們。
7.2 析構函數可以聲明為純虛函數,但必須定義。
1)polymorphic(帶多态性質的)base classes 應該聲明一個virtual析構函數。如果class帶有任何virtual函數,他就應該擁有一個virtual析構函數。
2)Classes的設計目的如果不是為了作為base classes使用,或不是為了具備多态性(polymorphically),就不應該聲明virtual析構函數。
1)析構函數絕對不要吐出異常。如果一個被析構函數調用的函數可能抛出異常,析構函數應該捕獲任何異常,然後吞下他們(不傳播)或結束程式。
2)如果客戶需要對某個操作函數運作期間抛出的異常做出反應,那麼class應該提供一個普通函數(而非在析構函數中)執行該操作。
9.1 間接調用也不可以。
在構造和析構期間不要調用virtual函數,因為這類調用從不下降至derived class(比起目前執行構造函數和析構函數的那層)。
10.1 由于右結合性,x=y=z=15等價于a=(y=(z=15))。
令指派(assignment)操作符傳回一個reference to *this。
11.1 通過this==&val來判斷是否是自指派。
11.2 即使已經處理了自我指派,operator=往往不具有“異常安全”性。異常安全可以通過精心安排的語句來保證,還可以使用copy and swap技術。C++11中還要考慮move assignment operator 情況。
A& A::operator=(A ai){
swap(ai);
return *this;
1)確定當對象自我指派時operator=有良好行為。其中技術包括比較“來源對象”和“目标對象”的位址、精心周到的語句順序、以及copy-and-swap。
2)确定任何函數如果操作一個以上的對象,而其中多個對象是同一個對象時,其行為仍然正确。
1)Copying函數應該確定複制“對象的所有成員變量”及所有base class成分。
2)不要嘗試以某個copying函數實作另一個copying函數。應該将共同機能放進第三個函數中,并由兩個copying函數共同調用。
3. 資源管理
13.1 以對象管理資源,即“資源取得時便是初始化時機”(Resource Acquisition Is Initialization;RAII),獲得資源即用來初始化(或指派)管理對象。
13.2 管理對象運用析構函數確定資源被釋放。
1)為防止資源洩露,請使用RAII對象,他們在構造函數中獲得資源并在析構函數中釋放資源。
2)兩個常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。。。 。補充:在C++11中,提供了std::shared_ptr,std::unique_ptr和std::weak_ptr來管理資源。
1)複制RAII對象必須一并複制它所管理的資源,是以資源的copying行為決定RAII對象的copying行為。
2)普遍而常見的RAII class copying行為是:抑制copying、施行引用計數法(reference counting)。不過其他行為也都可能被實作。
1)APIs往往要求通路原始資源,是以每一個RAII class應該提供一個“取得其所管理之資源”的辦法。
2)對原始資源的通路尅經由顯式轉換或隐式轉換。一般而言顯式轉換比較安全,但隐式轉換對客戶比較友善。
16.1 盡量不要對數組形式做typedef動作。typedef int IntArra[10]; IntArra *p = new IntArra;應該使用delete[] p。
如果你在new表達式中使用[],必須在相應的delete表達式中使用[]。如果你在new表達式中不使用[],一定不要在相應的delete表達式中使用[]。
17.1 當将newed對象置入智能指針的同時,還有多條不能确定執行順的語句,是很危險的。補充:在C++11下面,用make_ptr是最安全的用法。
//危險的用法
foo(shared_ptr<A>(new A), foo2());
//正确的用法
shared_ptr<int> p(new A);
foo(p, foo2());
以獨立語句将newed對象存儲于(置入)隻能指針内。如果不這樣做,一旦異常抛出,有可能導緻難以察覺的資源洩露。
4. 設計與聲明
1)好的借口很容易被正确使用,不容易被吳用。你應該在你的所有的接口中女裡達成這些性質。
2)“促進正确使用”的辦法包括接口的一緻性,以及與内置内的行為相容。
3)“阻止無用”的辦法包括建立新類型、限制類型上的操作,束縛對象值,以及消除客戶的資源管理責任。
4)shared_ptr支援定制型删除器(custom deleter)。這可防範Dll問題,可被用來自動解除互斥鎖等等。
1)盡量以pass-byreference-to-const替換pass-by-value。前者通常比較高效,并可避免切割問題(slicing problem)。
2)以上規則并不适用于内置類型,以及STL的疊代器和函數對象。對他們而言,pass-by-value往往比較合适。
絕不要傳回pointer或reference指向一個local stack對象,或傳回reference指向一個heap-allocated對象,或者傳回pointer或者reference指向一個local static對象而有可能同時需要多個這樣的對象。
22.1 原因:1)文法的一緻性,2)對成員變量的處理有更精确的控制,3)封裝。
1)切記将成員變量聲明為private。這可賦予客戶通路資料的一緻性、可細微劃分通路控制、允諾限制條件獲得保證,并提供class作者以充分的實作彈性。
2)protected并不比public更具封裝性。
甯可拿non-member、non-friend函數替換掉member函數,這樣做可以增加封裝性、包裹彈性(packaging flexibility)和技能擴充性。
24.1 這樣就成處理string str; "abc"+str; str + "abc"的(隐式轉換)問題。
如果你需要為某個函數的所有參數(包括被this指針所指的那個隐喻參數)進行類型轉換,那麼這個函數必須是個non-member。
25.1 在pimpl(pointer to implementation)類型的類中,如果采用深拷貝進行初始化、指派,但在swap時,就該使用淺拷貝。這就需要自己定義swap函數。補充:在C++11中,這更應該定義move constructor和move assignment。
25.2 客戶可以全特化std内的templates,但不可以添加新的templates(或者class或functions或其他任何東西)到std裡頭。
25.3 注意name lookup規則中的argument-dependent lookup。
1)當std::swap對你的類型效率不高時,提供一個swap成員函數,并确定這個函數不抛出異常。
2)如果你提供一個member swap,也應該提供一個non-member swap用來調用前者。對于classes(而非templates),請特化std::swap。
3)調用swap時應針對std::swap使用using declaration,然後調用swap并且不帶任何命名空間修飾符。
4)為”使用者定義類型“進行std templates全特化是好的,但千萬不要嘗試在std内加入某些對std而言全新的東西。
5. 實作
26.1 我們不僅應該延後變量的定義,知道非得使用的前一刻為止,甚至應該嘗試延後這份定義直到能夠給他初值實參為止。
26.2 這樣可以避免構造(和析構)非要對象,還能夠避免無意義的default構造行為。更深一層:以有意義的initializer來初始化,并附有說明變量的目的。
26.3 下面兩種形式,做法A:1構造 + 1析構 + n指派;做法B:n構造 + n析構。除非1)你知道指派成本比“構造+析構”成本低,2)你正在處理代碼中效率高度敏感(performance-sensitive)的部分,否則你應該使用做法B。
//做法A:定義于循環外
Widget w;
for(int i = 0; i < n; ++i){
w = 取決于i的某個值;
//做法B:定義于循環内
Widget w(取決于i的某個值);
27.1 在C++中,單一對象可能擁有一個以上的位址,尤其是在多繼承時(單一繼承也可能發生),是以,應避免做出“對象在C++如何布局”的假設。
1)如果可以,盡量避免轉型,特别是在注重效率的代碼中避免dynamic_cast。如果有個設計需要轉型動作,試着發展無需轉型的替代設計。
2)如果轉型是必要的,試着将他隐藏與某個函數背後。客戶随後可以調用該函數,而不需要将轉型放到他們自己的代碼内。
3)甯可使用C++-style(新式)轉型,不要使用舊式轉型。前者容易辨識出來,而且也比較有着分門别類的職掌。
28.1 handle主要指:reference、pointer和iterator。
28.2 傳回handle不僅有可能違反logical constness,而且就相當于公開内部成員,降低了封裝性,另外還有dangling handle的可能。
避免傳回handles(包括reference、pointer、iterator)指向對象内部。遵守這個條款可增加封裝性,幫助const成員函數的行為像個const,并将發生“虛吊号碼牌”(dangling handles)的可能性降至最低。
29.1 當抛出異常時,異常安全的函數會:1)不洩露任何資源,2)不允許資料敗壞。
29.2 異常安全函數提供一下三種保證之一:1)基本保證:抛出後,狀态是正常的,但處于哪個狀态不可預料;2)強烈保證:前後狀态保持一直;3)不抛擲保證。
29.3 聲明不抛出異常,并不是說起不會抛出異常,而是說如果抛出異常将是嚴重錯誤,會有意想不到的函數被調用(unexpected)。
29.4 在強烈保證的函數中,常使用copy and swap技術(通常效率不高);如果一個函數調用多個(強烈保證的)函數,他就很難保證是強烈保證。
29.5 由于強烈保證在效率、複雜度上的成本太高,對許多函數而言,“基本保證“一個更好的選擇。
1)異常安全函數(Exception-safe functions)即使發生異常也不會洩露資源或允許任何資料結構敗壞。這樣的函數區分為三種可能的保證:基本型、強烈型、不抛出異常型。
2)“強烈保證”往往能夠以copy-and-swap實作出來,但“強烈保證”并非對所有函數都可實作或具備現實意義。
3)函數提供的“異常安全保證”通常最高隻等于其所調用之各個函數的“異常安全保證”中的最弱者。
30.1 類内定義的成員函數,隐含是inline。在類内定義的friend函數,也隐含為inline。
30.2 inline是請求,編譯器可能忽略之。對于一個編譯器願意inlining的某個函數,編譯器還是可能為之生成一個函數本體(當通過inline函數的位址調用時)。
30.3 構造函數和析構函數往往是inlining的糟糕候選人。
30.4 inline的缺點:1)代碼量增大,2)程式庫更新不便,3)調試不便
1)将大多數inlining限制在小型、被頻繁調用的函數身上。這可是日後的調試過程和二進制更新(binary upgradability)更容易,也可使潛在的代碼膨脹問題最小化,使程式的速度提升機會變大。
2) 不要隻因為function templates出現在頭檔案,就将他們聲明為inline。
31.1 設計原則:1)如果使用object references或object pointers可以完成任務,就不要使用objects。2)如果能夠,盡量以class聲明式替換class定義式。3)為聲明式和定義式提供兩個不同的頭檔案。
31.2 對于參數或者傳回類型是object的函數,聲明時不需要object的定義式。
31.3 當聲明式和定義式提供兩個不同的頭檔案時,主要保持一緻性。如頭檔案<iosfwd>。本條款也适用于templates。
31.4 通常使用pimlp(pointer to implementation)技術來實作。這樣的類叫做Handle classes。
31.5 另外一個實作方式是通過interface classes。
31.5 Handle classes和interface classes實作,都會有一定程度的成本。
1)支援“編譯依存性最小化”的一般構想是:相依于聲明,不要相依于定義式。基于此構想的兩個手段是Hande Classes和Interface class。
2)程式庫頭檔案應該以“完全且僅有聲明式”(full and declaration-only forms)的形式存在。這種做法不論是否涉及templates都适用。
6. 繼承與面向對象設計
32.1 當Penguin繼承Bird時,Bird中不宜有fly接口。Square繼承rectangle不是好的設計。
“public 繼承”意味“is-a"。适用于base classes身上的每一件事情一定也适用于derived class身上,因為每一個derived class對象也都是一個base class對象。
33.1 轉交函數,就是在private繼承的derived類中,将virtual函數的override定義寫在public标簽下。
1)derived classes内的名稱會遮蓋base classes内的名稱。在public繼承下從來沒有人希望如此。
2)為了讓被遮蓋的名稱重見天日,可使用using declaration或轉交函數(forwarding functions)。
34.1 pure virtual函數、impure virtual函數、non-virtual函數分别為:隻繼承接口、繼承接口和一份預設實作、繼承接口和一份強制實作。
34.2 當pure virtual函數也有實作時,繼承接口+可以使用一份(預設)實作。
1)接口繼承和實作繼承不同。在public繼承之下,derived classes總是繼承base class的接口。
2)pure virtual函數隻具體指定接口繼承。
3)普通impure virtual函數具體指定接口繼承及預設實作繼承。
4)non-virtual函數具體指定接口繼承以及強制性實作繼承。
1)virtual函數的替代方案包括NVI(non-virtual interface)手法以及Strategy設計模式的多種形式。NVI手法自身是一個特殊形式的Template Method設計模式。
2)将機能從成員函數移到class外部函數,帶來的一個缺點是,非成員函數無法通路class的non-public成員。
3)tr1::function(在C++11中已經成為std::function)對象的行為就像一般函數指針。這樣的對象可接納“與給定之目标簽名式(target signature)相容”的所有可調用物(callable entities)
絕不重新定義繼承而來的non-virtual函數。
37.1 virtual函數系動态綁定,而預設參數值确實靜态綁定。
37.2 當virtual函數有預設值時,父類和派生類的預設值要一樣,這無疑加重的程式員的負擔,這是可以考慮virtual的替代設計。
絕對不要重新定義一個繼承而來的預設參數值,因為預設參數值都是靜态綁定,而virtual函數——你唯一應該覆寫的東西——卻是動态綁定。
38.1 public繼承帶有is-a的意義,而複合(composition,組合)意味着has-a或is-implemented-in-terms-of。
1)複合(composition)的意義和public繼承完全不同。
2)在應用域(application domain),複合意味has-a(有一個)。在實作域(implementation domain),複合意味is-implemented-in-terms-of(根據某物實作出)。
39.1 private繼承不能做derived-to-base的轉換。
39.2 private繼承在軟體“設計”層面上沒有意義,其意義隻及于軟體實作層面。private繼承意味着隻有實作部分繼承,接口部分應略去。
39.3 對于空白類,他的大小不為0,複合到其他類中時,也要占用一定空間;但是當作為其他類的基類時,并不占用空間。這就是所謂的EBO(empty base optimization)
1)private繼承意味着is-implemented-in-terms-of(根據某物實作出)。它通常比複合(composition)的級别低。但是當derived class需要通路protected base class的成員,或需要重新定義繼承而來的virtual函數時,這麼設計是合理的。
2)和複合(composition)不同,private繼承可以造成empty base最優化。這對緻力于“對象尺寸最小化”的程式庫開發者而言,可能很重要。
1)多重繼承比單一繼承複雜。他可能導緻行的歧義性,以及對virtual繼承的需要。
2)virtual繼承會增加大小、速度、初始化(及指派)複雜度等等成本。如果virtual base classes不帶任何資料,将是最具有使用價值的情況。
3)多重繼承的确有正當用途。其中一個情節涉及“public繼承某個Interface class”和“private繼承某個協助實作的class”的兩相組合。
7. 模闆程式設計與泛型程式設計
1)classes和templates都支援接口(interfaces)和多态(polymorphism)
2)對classes而言接口是顯示的(explicit),以函數簽名為中心。多态則是通過virtual函數發生于運作期。
3)對template參數而言,接口是隐式(implicit),奠基于有效表達式。多态則是通過template具現化和函數重載解析(function overloading resolution)發生于編譯期。
42.1 在template程式設計中,typename必須作為嵌套從屬類型的字首名,但是也有兩點例外:在base class list或member initialization list中。
template<typename T>
class Derived : public Base<T>::Nested{
Derived(int x):Base<T>::Nested(x){
typename Base<T>::Nested temp;
42.2 由于每次用到嵌套從屬類型都需要加typename,通常做法是定義别名。
void f(T t){
typedef typename std::iterator_traits<T>::value_type value_type;
value_type val(*t);
...
1)聲明template參數時,字首關鍵字class和typename可互換。
2)請使用關鍵字typename标示嵌套從屬類型名稱;但不得在base class list(基類清單)或member initialization list(成員初始化清單)内以它作為base class 修飾符。
43.1 當基類為模闆類時,并不能直接使用來自基類的名稱。基類模闆的通用版本,與基類模闆針對某個類型的特化版本,可能在内部name、接口上完全不一樣;是以編譯器無法判斷要使用的name是否來自基類模闆。
43.2 三個辦法使用基類的名字:1)名字前加sthis->;this->name()。2)使用前先using declaration一下name;using Base<T>::name;但是有對name修改通路權限的副作用。3)名字前直接加基類域限定符;Base<T>::name()。當name是virtual函數名時,這樣就會關閉virtual綁定行為,這是很糟糕的。當然,這三個方法的一個公共前提是:派生類對基類的這個名字有通路權限。
可在derived class templates内通過“this->"指涉base class templates内的成員名稱,或藉由一個明白寫出的”base class 資格修飾符“完成。
1)Templates生成多個classes和多個函數,是以任何template代碼都不該與某個造成膨脹的template參數産生相依關系。
2)因非類型模闆參數(non-type template parameter)而造成的代碼膨脹,往往可消除,做法是以函數參數或class成員變量替換template參數。
3)因類型參數(type parameters)而造成的代碼膨脹,往往可降低,做法是讓帶有完全相同二進制表述(binary representations)的具體類型(instantiation types)共享代碼。
45.1 在class内聲明泛化copy構造函數(member template)并不會阻止編譯器生成他們自己的copy構造函數。如果不希望使用合成的copy構造函數,就需要定義正常的copy構造函數。相同規則同樣适用于指派(assignment)操作。注意:定義了泛化的構造函數,編譯器就不會再合成預設構造函數了。
1)請使用member function templates(成員函數模闆)生成“可接受所有相容類型”的函數。
2)如果你聲明member template用于“泛化copy構造”或“泛化assignment操作”,你還需要聲明正常的copy構造函數和copy assignment操作符。
46.1 當用模闆實作“條款24”的功能時,往往存在很多錯誤。下面的方法1中,模闆參數無法比對,編譯無法通過;方法2中,根據oneHalf執行個體化了Rational<int>,friend函數operator*就别聲明出來了,現在operator*就不再是函數模闆,是函數了,支援實參的隐式轉換了,是以編譯可通過,但找不到定義(因為沒有定義operator*(const Rational<int>&, Rational<int>&),是以連結不通過。方法3中,分析同上,差別在于,執行個體化類的時候operator*也被定義了。将friend函數定義在類内,就隐含為inline,但如果想避開inline的弊端(條款30),可以按照做法4實作。
//============做法1============
class Rational{
Rational(const T& n, const T& d);
/* ... */
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
Rational<int> result = oneHalf * 2;//錯誤!無法編譯通過。
//============做法2============
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
Rational<int> result = oneHalf * 2;//編譯通過,連結錯誤!
//============做法3============
friend const Rational operator*(const Rational& lhs, const Rational& rhs){
Rational<int> result = oneHalf * 2;//編譯通過,連結通過!
//============做法4(非inline做法)============
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs){
doMultiply(lhs, rhs);
當我們編寫一個class template,而它所提供之“與此template相關的”函數支援“所有參數之隐式類型轉換”時,請将那些函數定義為“class template内部的friend函數”。
1)Traits classes使得“類型相關資訊”在編譯期可用。他們以templates和“templates特化”完成實作。
2)整合重載技術(overloading)後,traits classes有可能在編譯期對類型執行if...else測試。
48.1 上一條款中基于iterator_traits來做重載(條件選擇)也是template metaprogramming(TMP)的展現。
48.2 某些東西在TMP比在“正常的”C++容易。若将上一條款中的重載方式,替換成如下代碼,對于不支援 iter += d的類型就會編譯錯誤。因為編譯期必須確定所有源代碼有效,縱使是不會執行起來的代碼!是以模闆程式設計不宜與RTTI一起使用。
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d){
if(typid(typename std::iterator_traits<IterT>::iterator_category)
==typeid(std::random_access_iterator_tag))
iter += d;
else
1)Template metaprogramming(TMP,模闆元程式設計)可将工作由運作期移往編譯期,因而得以實作早期錯誤偵測和跟高的執行效率。
2)TMP可被用來生成“基于政策選擇組合”(based on combinations of policy choices)的客戶定制代碼,也可用來避免生成對某些特殊類型并不适合的代碼。
8. 定制new和delete
49.1 new_handler在std中定義:
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
49.1 一個設計良好的new-handler函數,應該做一下事情:1)讓更多記憶體可被使用;2)安裝另一個new-handler;3)卸除new-handler;4)抛出bad_alloc,這樣的異常不會被operator new捕捉,是以會被傳播到記憶體索求處;5)不傳回,通常調用abort或exit。
1)set_new_handler允許客戶指定一個函數,在記憶體配置設定無法獲得滿足時被調用。
2)Nothrow new是一個頗為局限的工具,因為它隻适用于記憶體配置設定 ;後續的構造函數調用還是可能抛出異常。
有許多理由需要寫一個自定的new和delete,包括改善效能,對heap運用錯誤進行調試、收集head的使用資訊。
1)operator new應該内含一個無窮循環,并在其中嘗試配置設定記憶體,如果它無法滿足記憶體需求,就該調用new-handler。它也應該有能力處理0bytes申請。class專屬版本則還應該處理“比正确大小更大的(錯誤)申請”。
2)operator delete應該在收到null指針時不做任何事。class專屬版本則還應該處理“比正确大小更大的(錯誤)申請。
1)當你寫一個placement operator new,請确定也寫出了對應的placement operator delete。如果沒有這麼做,你的程式可能發生隐微而時斷時續的記憶體洩露。
2)當你聲明placement new和placement delete,請确定不要無意識(非故意)地掩蓋了他們的正常版本。
9. 雜項讨論
1)嚴肅對待編譯器器發出的警告資訊。努力在你的編譯器的最高(最苛刻)警告級别下争取”無任何警告“的榮譽。
2)不要過度依賴編譯器的報警能力,因為不同的編譯器對待事物的态度并不相同。一旦移植到另一個編譯器上,你原本依賴的警告資訊有可能消失。
1)C++标準程式庫的主要機能有STL、iostreams、locales組成。并包含C99标準庫。
2)略。。。(注:應該參考C++11的新特性)
3)TR1自身隻是一份規範。為獲得TR1提供的好處,你需要一份禮物。一個好的事物來源是Boost。
1)Boost是一個社群,也是一個網站。緻力于免費、源碼開放、同僚複審的C++程式庫開發。Boost在C++标準化的過程中扮演深具影響力的角色。
2)Boost提供許多TR1元件實作品,以及其他許多程式庫。