Effective C++ 條款總結


 自己在看這本書的時候,回去翻看目錄的時候,有些規則會被遺忘,因此做個簡單的小總結供自己和其他人參考,沒讀過的還是要先去讀一遍的

 

一.讓自己習慣C++

1.視C++為一個語言聯邦

  C++是一種包含許多特性的語言,因而不要把它視為一個單一語言。理解C++至少需要學習一下4個部分:

   ①C語言。C++仍以C為基礎

   ②objected-oriented C++。面向對象編程,類、封裝、繼承、多態

   ③template C++。C++泛型編程、模板元編程的基礎

   ④STL。容器、迭代器、算法

2.盡量使用const等替換#define

  這條個人覺得沒必要,有許多功能除了宏是很難實現的,像許多優秀的開源項目,例如tensorflow中,就大量使用的宏。

3.盡可能使用const

  const可以作用於:變量、指針、函數參數類型、類中的常函數。const可以防止變量被意外的修改,有助於編譯器檢測這些意外改變。

  ②當non-const和const實現相同邏輯時,non-const對象可以調用const成員函數,這樣可以縮減代碼量。另外注意const對象不能調用non-const成員函數,編譯報錯:discards qualifiers。

4.確定對象使用前被初始化

  這里提到一個重要的基本概念:

  在構造函數的初始化列表中的才算是初始化,而構造函數的內容是在初始化列表之后執行的,已經不算是初始化操作。這里就存在效率問題,假設你的類成員是個其他類的對象,比如std::string name,你在初始化列表中進行初始化,調用的是string的拷貝構造函數,而在構造函數中進行賦值的話,調用的是:默認構造函數+賦值函數,調用默認構造的原因是,調用構造函數之前會先對成員進行初始化(這也就是為什么在構造函數中進行的操作不能稱之為初始化操作),而對於大多數類,默認構造函數+賦值函數的效率是小於只調用拷貝構造函數的。

  ①因此最好是在初始化列表中進行初始化操作。

  ②內置類型要手動初始化,不同的平台不能保證對內置類型進行初始化

二.構造/析構/賦值運算

5.C++默認編寫並調用的函數

  譯器默認實現的函數:默認構造、析構、拷貝構造、賦值函數。這里注意深拷貝和淺拷貝問題。

6.不想使用默認生成的函數,可以明確拒絕

  默認的構造可以被其他構造替換,拷貝構造和賦值函數如果不想被外面調用可以將其聲明為private。

7.多態基類聲明virtual析構函數

  當一個父類指針指向子類對象時(即多態的用法),在釋放對象時,如果父類的析構函數不是virtual的,那么編譯器會將這個指針視為父類類型的,只會釋放掉這個對象的一部分空間。如果聲明為virtual的,那么在釋放的時候,編譯器就知道這是一個子類類型,會將對象都釋放掉,即防止內存泄漏問題。

8.別讓異常逃離析構函數

  絕不要讓析構函數拋出異常,應該讓用戶自己處理可能發生異常的操作

9.不要在構造和析構函數調用virtual函數

  由於父類的構造函數發生在子類之前,而此時子類的成員變量等並未初始化,因此在父類的構造函數中調用virtual函數,絕對不會調用子類的方法,即使現在你在創建一個子類對象。換句話說,在構造函數中調用的virtual函數,都會下降到父類類型,即都不是virtual函數。同樣的道理,子類析構函數調用在父類之前,因此在父類析構函數調用virtual函數時,子類都不存在了,你讓編譯器怎么調用。因此一定不要再構造和析構中調用virtual函數。

10.令operator=返回reference to *this 並 11處理自我賦值

  第一條一般都這么寫,沒毛病,返回引用比臨時變量要少幾次構造析構,效率高;第二條是因為,賦值的時候要先釋放自己的資源然后賦予新的資源(資源假設為一個指針,這樣便於理解),如果你自己給自己賦值,按照這個先釋放再賦值的邏輯,自己直接就沒了。所以發現是自己賦值自己的時候(this = &object)直接返回*this即可。

12復制對象的時候勿忘其每個部分

  沒啥可解釋的

三.資源管理

 13.以對象管理資源

  ①在構造中獲得資源並在異構函數中釋放資源

  ②兩個常用的自動管理資源的類是shared_ptr和auto_ptr,其中auto_ptr的復制動作,會導致復制對象變為null,容易造成意外的錯誤,一般推薦使用shared_ptr,其使用引用計數的原理實現對象共享的目的,並且在計數為0時自動釋放對象

14.在資源管理類中小心copy行為

15.在資源管理類中提供對原始資源的訪問

16.成對的使用new和delete

  使用new申請內存,必須使用delete釋放內存,使用new [] 申請內存,必須使用delete []釋放內存

17以單獨的語句將newed對象置入shared_ptr

  意思為不要寫下類似Function(shared_ptr<Class>(new Class), x()),其中Function和x為函數,Class是一個類。原因在於shared_ptr的構造分為兩步,第一步是new Class創建一個對象,第二部是執行構造函數,但是像上面那樣寫可能導致x()在兩步之間運行(與編譯器如何編譯代碼有關了),如果這個時候x()發生了異常,那么就會導致內存泄漏。因此初始化shared_ptr的時候,最好不要摻雜其他東西。

四.設計與生命

18.讓接口容易被正確使用,不易被誤用

  文章舉了個Day,Month,Year的例子進行簡單的說明。在實際中就是要考慮客戶如果使用你寫的接口,怎么樣能降低錯誤使用率,就像給別人提供API一樣。要做到這一點還是很不容易的。

19.設計class

  這個真的很難一開始就設計的很好,一般都是根據需求慢慢調整的,但是有一些公共特征還是考慮,比如構造和析構,需要什么操作等。

20.以pass-by-reference-const替換pass-by-value

  主要考慮兩點:

  ①效率問題,pass-by-value會導致很多臨時對象的產生和銷毀,就會多調用幾次構造和析構,因此效率更低

  ②對象切割問題,pass-by-value的方式將一個子類對象傳入一個參數類型父類的函數,那么拷貝的對象將被切割成只有父類對象被保留。引用可以解決這個問題,因為引用本質上也是指針,就和多態是一樣的

  另外,內置類型,STL迭代器和函數對象一般采用pass-by-value,因為其復制代價很小

21.不要返回臨時對象的引用

  ①不要返回一個臨時對象的引用

  ②不要返回在堆上分配的對象的引用,因為這違背了new和delete成對出現的原則,這樣的方式是很不合理的,稍加不注意就會導致內存泄漏問題。

  ③也不要返回一個static對象的引用,因為static可能同時被很多地方需要,這樣的話共享就存在問題。

  所以對於這種問題,最好的解決方法就是不返回引用就OK了。

22.將成員變量聲明為private

  常規操作

23.寧以non-member、non-friend函數替換member函數

  文中提到了封裝性強弱的概念:一個類中的成員或者成員函數,被越少的代碼訪問,那么封裝性越高。所以增加一個成員函數會增加訪問成員變量的代碼,因此降低了封裝性。所以這個條款才如此建議。仁者見仁智者見智,這個條款目前樓主沒有在實際項目中應用過,大部分還是以成員函數的方式實現,因此個人感覺這個條款並沒有感覺有什么卵用!

24.若所有參數皆需類型轉換,那么請采用non-member函數

  文中舉了一個有理數Rational運算的例子,在類中加入一個operator*(const Rational& other)的函數,可以實現類似 rational * 2的操作,其中2是個int,但是因為rational有一個以int為參數的構造,因此編譯器幫你執行了隱式類型轉換。但是反過來寫2 * rational的時候,編譯就報錯了。因為2是個int,並沒有operator*這個函數。但是為什么這樣寫就沒有執行隱式類型轉換呢?這又引出一個問題:隱士類型轉換的合格條件是什么?答案是:必須是參數列中的參數才是隱士類型轉換的有效參與者,類的執行者也就是'.'前面的那個對象(this指向的對象,比如說rational.func()中的rational是類執行者,相當於他是函數的調用人,地位較高,不能參與隱式類型轉換),這就解釋了為什么2放在前面是不行的。解決此種問題的方法是提供一個non-mem的operator*(Rational a, Rational b)即可。

25.寫一個不拋出異常的swap函數

  一般寫swap最普通的方法就是利用中間變量,temp = a;a = b;b = temp,這種方法對於內置類型沒任何問題,內置類型上的賦值絕對不會拋出異常,並且效率很高。但是如果a,b不是內置類型,就會調用類的copy構造函數和assign函數,並且必須是深拷貝。這樣如果類的成員較多就會造成交換的效率很低,特別是針對pimpl實現方法,即成員中包含指針(即資源)時。更好的做法就是直接交換指針就可以了,相當於交換了兩個int(指針都是4字節的),這就比拷貝這個指針指向的資源要快得多。

  如何實現呢?只要將swap都轉換成內置類型的swap就可以了,做法就是在類中提供一個public的swap(T& b)函數(T為一個類),將每個成員進行交換(如果成員中包含其他非內置對象,調用這個對象的swap函數即可)。然后提供一個non-member的swap(T& a, T& b)重載函數,在函數內部調用類中的a.swap(b),就可以像如下方式實現交換兩個對象的操作:swap(a, b)。

  注意:

  ①在類內的swap交換內置類型時要調用std命名空間內的swap函數,必須使用using std::swap,否則就變成遞歸函數了

  ②另外文中說在std命名空間內不能加入新東西,比如重載swap函數,但是經博主測試是可以在std內重載swap函數的(g++版本為5.4.0)。

五.實現

 26.盡可能延后變量定義得時間

  ①因為變量(對類而言)的定義,需要承擔一次構造函數的時間,在函數結束后還可能承擔一次析構函數的時間,假如該變量未被使用,那么構造函數和析構函數的時間就白白浪費了,尤其是在可能發生異常的函數中,假如你過早的定義變量,然后在你使用這個變量之前拋出了異常,那么這個變量的構造函數就沒有意義而且降低效率。所以應該盡可能延后變量定義得時間,只有真正使用這個變量的時候才定義它

  ②條款4講過,copy construction的效率 > default construction +assign function,所以最好的做法是直接調用copy construction函數對變量直接進行初始化,而不是先定義,再賦值

  ③對於有循環的情況,假設一個n次的循環,如圖所示:

  

  那么方法A的代價:1次構造+1次析構+n次賦值

  方法B的代價:n次構造+n次析構

  如果n較大,那么應該選擇方法A,如果n較小,可以選擇方法B。

 27.盡量避免轉型

  ①最好使用C++4個新式的類型轉換函數,因為這很容易辨識,代碼可讀性提高

  ②盡量避免使用dynamic_cast,因為這種轉換效率很低,一般用虛函數的方式來避免轉型

28.避免返回一個指針、引用或者迭代器指向類內的成員

  原因是如果返回了成員的引用或者指針,就可以通過這個引用或者指針修改雷內的private成員,這樣是不合理的(這樣的話成員就相當於public的了),這一點可以通過給函數的返回類型加const修飾符來防止內部成員變量被修改。但是還有一種情況是,如果獲得的類內的一個成員的引用或指針,但是在使用之前,對象被釋放了,那么這個引用或指針就變成了野指針了,必然會導致core dump錯誤。所以應該避免返回類內成員的指針或引用。

29.異常安全函數

  發生異常時的處理主要分一下幾類:資源不泄漏、數據不丟失、不拋出異常。反正就是考慮程序的各種可能的情況,如果異常了要盡可能保證你的程序某些功能或數據不丟失。這個也沒啥可說的。

30.inline 函數

  inline只是一種申請,編譯器會根據具體情況來決定一個函數是否可以是inline得,比如遞歸函數、virtual函數、代碼較多的函數,即使你聲明了inline關鍵字,編譯器也不會將此類函數視為inline的函數。

31.編譯依存關系降低至最低

  理解此條款關鍵是要理解C++的編譯方式,具體可以參考:https://www.cnblogs.com/jerry19880126/p/3551836.html文中說的很詳細。理解了文中的意思,再回頭看這個條款就清晰多了。其關鍵點如下:

  ①關於前置聲明的一個限制是:編譯器必須在編譯時確定類的大小,即分配多少內存。因此如果你前置聲明一個類class TEST,然后在后面試圖創建一個TEST的對象TEST test,那么這個代碼是不會通過編譯的,因為編譯器不確定要給test分配多少內存。那怎么規避這個問題呢?答案就是指針,典型的pimpl方式,因為指針的字節數是固定的(相對於平台,一般就是4或者8字節)。例如下面的代碼就是可以通過編譯的。

#include <iostream>
using namespace std;
class TEST;
class AA{ 
    public:
        TEST* aa; 
        //TEST& b; //引用不可以,因為引用必須在初始化列表中進行初始化
        void T(TEST& tt) { 
        }   
        void wdt(TEST* tt) {
        }   
        void PP() {
            cout << "AA::PP()" << endl;
        }   
        AA() {}
        ~AA() {}
};

int main() {
    AA aa; 
    aa.PP();
    return 0;
}

 

 可見,前置聲明一個TEST類,並沒有對應的類的實現,在另外一個類A中聲明一個TEST*的成員,包括在函數中使用TEST* 或者 TEST&類型的參數都沒問題。但是在用這兩個函數的時候還是要有TEST的定義和實現,那么這個時候怎么辦,就是再創建一個TEST.h和TEST.cc,然后在A.cc中#Include"TEST.H"(假如class A也單獨創建一個A.cc和A.h),這樣當TEST中的內容有所改變的時候,只有TEST.cc和A.cc被重新編譯。所有使用class A和class TEST的地方都不會被重新編譯。假如使用這兩個類的地方特別多,那么這樣就可以減少很多文件的編譯了。

  ②上面利用指針是一種實現方法,另一種就是Interface class,即類中全部都是pure virtual函數,這樣的類在使用的時候只能是以指針的形式出現,這樣就同樣達到了減少編譯依賴的效果。

  ③當然這兩種方式都存在一定的代價:指針方式的實現要多分配指針大小的內存,每次訪問都是間接訪問。接口形式的實現方式要承擔虛函數表的代價以及運行時的查找表的代價。但是一般這兩種實現對資源和效率的影響通常不是最關鍵的,因此可以放心的使用,類似tensorflow源碼中就大量使用這種方式降低編譯依賴。

六.繼承與面向對象設計

32.確保public繼承是is-a關系

33.名稱遮掩問題

  子類會遮掩父類同名的函數,可以使用類名作用域決定調用父類還是子類的函數。

34.接口繼承與實現繼承

  理解接口繼承和實現繼承的區別,純虛函數、非純虛函數和普通函數在這兩方面的區別:純虛函數只指定接口繼承、非純虛函數指定接口繼承並存在默認的實現繼承、普通函數指定接口繼承及強制實現繼承。

35.考慮virtual函數以外的選擇

  ......

36.不要重新定義繼承來的non-virtual函數

  non-virtual在實現上是靜態綁定的,調用父類還是子類的函數完全取決於指針或者對象的類型。在子類重定義non-virtual時,父類的相同的函數是不會被覆蓋的。這條與33條類似。

37.不要重新定義重寫函數(virtual)的默認參數

  默認參數都是靜態綁定的,即你的指針是什么類型,默認參數就是什么類型。而virtual函數是動態綁定的,在運行期才決定調用哪個函數。所以如果你在父類class Father有一個virtual函數並帶有默認參數,例如void p(int default = 100),在子類重寫這個函數,然后換了新的默認參數為default = 10,在你以多態的方式調用p的時候:Father* f = new Son; f->p();這種情況p的默認參數為100而非10。因為f指針的靜態類型為Father,而動態類型為Son。所以如果你的函數必須包含默認參數,不要這樣寫,解決方法是將帶有默認參數的函數改為non-virtual函數,內部再調用一個virtual函數。因為non-virtual函數是從來不應該被重寫的(條款36,覆蓋問題)

 38.類與類之間的關系:復合(has a的關系)

39.私有繼承

  子類繼承父類的方式決定了在子類中父類函數的屬性,一般規則就是所有屬性都按照繼承方式對其。比如采用protected繼承方式,那么父類中的public成員在子類都升級為protected,其他保持不變。如果采用private繼承方式,父類中的所有成員全部變為private,特殊之處之一是父類中原本就是private的成員不可繼承,即在子類中也無法使用父類的private成員。

40.多重繼承

七.模板與泛型編程

41.隱式接口和編譯器多態

  class的繼承和template都支持接口和多態。只不過class實現的接口是顯示的,就是說一定能直接找到這個接口的實現代碼。而template實現的接口,只能模糊的知道接口的特征,一般間接能找到實現的代碼。用繼承實現的多態屬於運行期多態、模板實現的多態則是編譯期多態。

42.了解typename

  ①在聲明template參數時,class和typename可互換。

  ②typename的第二個用處是告訴編譯期某一個嵌套從屬類型是類型,最典型的就是STL中容器的迭代器類型,例如:T::iterator(T是個容器的類型,例如:vector<int>),這個時候就要在T::iterator前面加一個typename,告訴編譯器這是一個類型,否則編譯器不能確定這是什么,因為有可能iterator是個靜態變量或者某一namespace下的變量。

  ③類的繼承列表初始化列表中的類型不需要typename指定類型,因為繼承的一定是個類,而初始化列表一定是調用父類的構造或者初始化某個成員。

43.調用基類模板成員

  當一個類的基類包含模板參數時,需要通過this->的方式調用基類內的函數,例如 class F: public S<C>,在F中的成員函數中調用S中的成員函數this->test(),而直接寫test()無法通過編譯,原因是因為C是個模板沒有辦法確定類S的具體長相,或者說無法確定S中一定有test函數,即使你寫的所有C都包含test函數,但是在編譯器看來它是不確定這個問題的,因此無法通過編譯。

  解決辦法是:①使用this->test,這樣做告訴編譯器假設這個test已經被繼承了。②使用using聲明式:using S<C>::test告訴編譯期這個test位於S內。相當於必須手動通知編譯器這個函數是存在的。

44.將與template參數無關的代碼抽離到模板外

  原因是模板會根據具體類型具象化不同的代碼,如果將與模板無關的代碼也放入模板函數或者類中,那么就會生成重復的代碼,就會導致代碼膨脹的問題,函數模板中與參數無關的代碼可以包裝成單獨的函數。類模板中與參數無關的模板可以放到父類中。

45.運用成員函數模板接受所有兼容類型

  文中以shared_ptr為例講解了使用成員函數模板實現智能指針不同類型間的轉換,以及如何避免任意類型之間的相互轉換,這種函數可稱為泛化拷貝構造函數。另外泛化拷貝構造不同於默認拷貝構造函數。

46.需要類型轉換時請為模板定義非成員函數

   條款24中Rational函數僅以int為例,說明了隱士類型轉換的合格參與者的條件,並提出了非成員函數的解決方法。現在將其擴展為template形式:

 

template <typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) {……}

 

  發現在調用的時候無法通過編譯,即以下代碼無法通過編譯:

Rational<int>   oneHalf(1, 2);
Rational<int>    ret = oneHalf*2;

  相比於條款24,換成模板之后為什么就無法通過編譯了呢?原因在於模板的運行需要進行模板推算,即operator*函數的兩個參數類型的T要根據傳入的參數類型進行確認,第一個參數因為是oneHalf,其本身就是Rational<int>類型,因此第一個參數的類型中的T很容易進行推理,但是第二個傳入的參數是int,如何根據這個int參數推導出第二個參數的類型T呢?顯然編譯器無法進行推理,條款24能推理的原因是進行了隱士類型轉換,或者說Rational的構造函數中有一個以int為參數的構造函數,但是template在進行參數推到的過程中從不將隱士類型轉換函數考慮在內,這也是合理的因為你沒法根據參數類型推導出模板參數,這個Ratinal的例子貌似看起來可以,因為構造函數的參數類型是const T& 但是假如其構造參數類型是個固定類型,比如說float,那么難道模板參數能永遠是float么。因此編譯器不考慮隱士類型轉換也是有道理的。

  那么這個問題怎么解決呢,該如何讓這個模板函數的參數能進行隱式類型轉換,答案就是:先具象化這個函數,相當於先確定T,然后就可以進行隱士類型轉換了,做法是在類中聲明一個非成員函數,這該如何做到呢,答案就是友元函數,在類中定義的友元函數都被視為非成員函數,對於本例該像如下方式聲明:

friend const Rational operator*(const Rational& lhs, const Rational& rhs) {
   return  (lhs.numrator()*rhs.numrator()/lhs.denominator()*rhs.denominator());  
}

  由於此函數是在模板類的內部,因此當oneHalf對象生成之后,T就被確定為int,那么operator*函數的參數和返回值中的T也均是確定的了。

  另外,由於此函數的功能過於簡單,因此可直接將其實現放入類中(inline的),假如類的功能很復雜,那么一般都采用調用類外的某一個功能函數,這時候代碼這樣實現:

friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) {
      return  DoMultiply<T>(lhs, rhs);  
}

  這樣寫實際和上面的寫法是一樣的,當T被確定為int時,會生成一份Rational<int>的類,具象化出里面的函數,這樣就能確定調用的是T為int的operator*函數,然后在另一個模板函數內實現其操作,本例來說就是DoMultiply函數。

47.traits編程技巧

  traits是用來獲取參數類型信息,因為有時候需要根據參數類型信息做不同的處理,下面這篇博客中列舉了兩個簡單的例子,https://blog.csdn.net/my_business/article/details/7891687(其實可以使用typeid進行簡單的實現,但是這種做法效率低,因為typeid需要配個if使用,if是在運行期才決定的,而traits可以在編譯器就進行類型的判別,效率更高),文中以STL迭代器相關函數中的advance為例,advance函數原型為:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT& d) {
  if(iter 為 random_access_iterator_tag) 
    iter += d;
  ……      
}

  其中要根據iter為不同類型(一共5種)執行不同操作,這時候就必須能提供一種判別類型的方法,前面說了利用typeid的方法,要使用if進行判斷,if是在運行期實現的,效率低,而且typeid有可能在編譯器就確定了類型,卻要等到運行期if才能決定運行哪個分支,為什么不在編譯器就定好呢。traits技術可以在編譯器就做到類型的判別,標准做法是聲明一個traits class然后進行各種不同版本的特化,本例中的traits class為(說是class,其實一般都聲明為結構體):

template<typename IterT>
struct  iterator_traits;

然后針對不同類型特化不同的struct,但是每個iterator類必須有一個相同的typedef 名為 iterator_category,iterator_traits只是將其再typedef成iterator_category

template <typename Iter>
struct iterator_traits {
   typedef typename Iter::iterator_category iterator_category;  
}

  也就是說iterator_traits中存儲了一個類型為iterator_category,但是根據不同的模板參數其有不同的值,這個例子,其實就是將類型統一名稱,當然traits的功能遠非如此,你可以根據需求添加相應功能。現在通過調用:iterator_traits<IterT>::iterator_category就拿到了迭代器的類型,那么怎么在編譯期做到判斷if呢,方法就是重載函數,重載函數是在編譯期就確定了調用哪個的,原理就是和所有名字相同的重載函數比較,直到找到參數類型一致的,這就是編譯期實現了if判斷,可以利用這個特點在編譯期就決定advance函數要運行什么功能。例如random的DoAdvance方法如下:

template <typename IterT, typename DistT>
void DoAdvance(IterT& iter, DistT& d, std::random_access_iterator_tag) {
  iter += d;
}

  在調用的時候,將advance函數改為:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT& d) {
  DoAdvance(iter, d, iterator_traits<IterT>::iterator_category());         
}

  這樣根據參數的不同就在編譯確定好了,你到底調用的是哪個DoAdvance函數。這里有個問題就是如果不用iterator_traits,DoAdvance函數的第三個參數直接換為 IterT::iterator_category也是可以,那么這么麻煩的用traits干嘛?其實對本例來說還有一種IterT是指針迭代器的情況,指針迭代器都屬於random分類,這種情況,不用trait就很難實現了,如果用traits的話,寫一個偏特化就OK了,例如:

template<typename IterT>
struct iterator_traits<Iter*>{
typedef  random_access_iterator_tag iterator_category;
}

有點類似前面提到的博客中,如何判定一個類型是指針類型。

另外拋開本例,在traits中也可以附加其他額外信息:就類似如何判定T是否是指針類型,就在traits中放入了一個bool;還有一種需求是聲明一個和參數類型相同的臨時變量,這時候不用traits就很難實現了,根據你的需求你可以任意添加附加信息實現更復雜的功能。

48.模板元編程

  采用模板編程的好處是:①可將工作由運行期移動到編譯器完成,造成更高的執行效率(占用內存小,運行速度快)和更早的偵測錯誤②編碼更加簡潔;壞處:①編譯時間長②代碼不易理解

八.定制new和delete(條款49~52)

   這章講了new和delete的一些高級用法:set_new_handler、operator new/delete的重載及應該遵循的規則、placement new。一般情況下較少會重載new,所以倒不如了解new/delete的基礎知識更好,參考:https://www.cnblogs.com/deepllz/p/9927807.html

九.雜項討論(條款53~55)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM