1. 程式人生 > >c++ primer 第十九章特殊工具與技術

c++ primer 第十九章特殊工具與技術

c++ primer 第十九章特殊工具與技術

19.1 控制記憶體分配

19.1.1 過載new和delete

呼叫new表示式執行了三步操作:第一步,呼叫名為operator new的標準庫函式分配一塊記憶體空間。第二步,編譯器執行建構函式傳入初始值。第三步,物件分配空間並構造完成,返回指向它的指標。

呼叫delete表示式執行了兩步操作:第一步,對引數對應空間的物件執行解構函式。第二步,呼叫operator delete釋放空間。

可以自定義operator new與operator delete函式。既可以是全域性函式,也可以是成員函式。優先在分配的類的作用域裡查詢,之後到全域性,再到標準庫。使用::new可以直接呼叫全域性作用域的版本。

operator new介面和operator delete共有8個介面,4個可以丟擲異常,另外四個承諾不丟擲。自定義這8個版本的函式必須在全域性作用域或類作用域,定義為類成員函式時會隱式宣告為靜態型別。

也可以自定義以上8個介面以外的自定義版本,但是不能過載void *operator new(size_t, void*);

這個函式。

分清operator new和new表示式的區別。new和delete符號的意思不能變。

malloc與operator new類似,free和operator delete類似。

19.1.2 定位new表示式

除了new和delete函式,也可以直接呼叫operator new和operator delete函式。

operator new與allocator類類似,但是operator new分配的空間無法使用construct函式構造。

定位new可以用來構造物件。當僅通過一個地址值呼叫時,定位new使用operator new(size_t, void*)分配記憶體。這個函式僅僅返回指標實參而不是真正的分配。定位new允許我們在一個特定的、預先分配的記憶體地址上構造物件。

定位new與construct的區別在於,傳遞的指標無需是allocator分配的。

顯示的解構函式呼叫與其它成員函式沒有區別。顯示呼叫解構函式不會釋放該物件所在的空間。

19.2 執行時型別識別

執行時型別識別(RTTI)由兩個運算子實現:

  • typeid運算子,返回表示式的型別。
  • dynamic_cast運算子,將基類指標或引用安全轉換為派生類的指標或引用。

RTTI運算子一般在無法使用虛擬函式時候使用。使用時需要清楚轉換的型別並檢查是否轉換成功。

19.2.1 dynamic_cast 運算子

dynamic_cast運算子的引數需要滿足三個條件之一:

  1. e的型別是目標type的公有派生類
  2. e的型別是目標type的公有基類(但指向的物件應該也有限制)
  3. e的型別就是type的型別。

返回結果是0的時候表示轉換失敗,在轉換引用失敗是丟擲bad_cast。

指標型別的dynamic_cast

if(Derived *dp = dynamic_cast<Derived*>(bp)) 
{}
else {}

引用型別的dynamic_cast

void f(const Base& b) {
    try{
        const Derived&d = dynamic_cast<const Derived&>(b);
        } catch(bad_cast) {
        }

19.2.2 typeid 運算子

typeid(e)返回e的型別,e可以是任意表達式或者型別的名字。

使用時忽略頂層const,不會把陣列或函式自動轉為指標。

對於沒有虛擬函式的類返回靜態型別,否則檢查執行時型別。

typeid作用於物件,因此檢查指標指向的物件型別時需要解引用。

typeid是否需要進行執行時檢查決定了表示式是否被求值。

19.2.3 使用RTTI

檢查一個派生體系裡的物件是否相等,單純使用虛擬函式equal無法完成,因為引數必須是基類指標或引用,此時無法呼叫派生類物件的成員。因此需要配合dynamic_cast使用。而先使用typeid比較型別也可以直接判斷不同型別的物件。

19.2.4 type_info類

type_info類的精確定義根據編譯器有所不同。

type_info沒有預設建構函式,拷貝和移動建構函式是刪除的。建立type_info物件唯一方式是使用typeid運算子。typeid的呼叫返回結果是一個type_info物件。

19.3 列舉型別

列舉型別可以將一組整型常量組織在一起。每個列舉型別也是一個新的資料型別。列舉屬於字面值常量型別。

C++中有兩種列舉:限定作用域的列舉型別使用enum class加名字和花括號初始值。enum class open_models {input, output, append};
不限定作用域的列舉型別省略掉class,名字可選。

限定作用域的列舉型別的列舉成員作用域與常規相同,列舉型別的作用域外不可訪問。而不限定作用域的列舉型別中,列舉成員可以在當前作用域直接訪問。

預設情況列舉成員的值從0開始依次加1,但是列舉值也可以指定而且可以不唯一。

可以把列舉型別用到switch語句,非型別模板形參或者類的初始化靜態資料成員。

enum可以定義新的型別。非限定作用域的列舉成員可以隱式轉換為int,限定作用域的不可以。int不能直接轉換為列舉型別。

enum可以定義列舉成員的資料型別,限定作用域的預設為int,不限定作用域的沒有預設型別而是足夠大。

enum型別可以提前宣告。聲明裡必須指定成員的大小。不限定作用域的必須指定型別,而限定作用域的隱式為int。定義需要與宣告匹配。

初始化一個enum物件必須使用另一個enum物件或者列舉成員。不能將整型實參傳給enum形參,但可以將不限定作用域的enum成員或物件傳給整型形參。一般提升為int。

19.4 類成員指標

成員指標指可以指向類的非靜態成員的指標。成員指標指向某個類的成員但不指定所屬的物件,直到使用時才提供物件。

const string Screen::*pdata;宣告的是指向Screen類中string成員的指標。
賦值為pdata = &Screen::contents;

使用成員指標時使用.*和->*兩種指標訪問運算子,分別對應物件型別呼叫與指標型別的呼叫。

常規的訪問控制規則對成員指標同樣有效。因此不能在類外部指向類的私有成員。

函式可以返回類成員指標。

19.4.2 成員函式指標

指向成員函式的指標最簡單的是使用auto,如auto pmf = &Screen::get_cursor;

指向成員函式的指標同樣需要說明返回型別和形參,如果是const形式或引用形式,也需要包含。

char (Screen::*pmf2)(Screen::pos, Screen::pos) const;
pmf2 = &Screen::get;

與普通函式指標不同,成員函式和函式指標之間不存在自動轉換。

呼叫成員函式指標

Screen myScreen, *pScreen = &myScreen;
char c1 = (pScreen->*pmf)();
char c2 = (myScreen.*pmf2)(0,0);

使用成員指標的類型別名或typedef讓成員指標更容易理解:
using Action = char (Screen::*)(Screen::pos, Screen::pos) const;
Action表示這個對應型別函式的指標形式。Action get = &Screen::get;

指向成員函式的指標也可以作為函式返回型別或形參,而且可以有預設實參。

成員指標函式可以作為一個指標型別陣列,對應不同情況呼叫不同的函式。

19.4.3 將成員函式用作可呼叫物件

和普通函式指標不同,成員函式指標不是一個可呼叫物件。

當我們使用find_if之類的標準演算法時,傳遞成員函式指標無法被呼叫。

使用function模板庫型別可以自動根據引數調整函式指標的呼叫形式。

function<bool (const string&)> fcn = &string::empty;
find_if(svec.begin(),svec.end(),fcn);

若it為svec的迭代器,那麼傳入普通函式指標時呼叫fcn(*it),這對成員函式指標不適用。而使用了function模板庫之後,如果傳入成員函式指標,會相當於呼叫((*it).*p)()。其中p是fcn中儲存的成員函式指標。

使用men_fn函式也可以讓編譯器來推斷成員的型別。使用men_fn(&string::empty)生成一個可呼叫物件,接受一個string實參,返回一個bool。

也可以使用bind生成一個可呼叫物件。auto it = find_if(svec.begin(), svec.end(), bind(&string::empty,_1));

19.5 巢狀類

定義在一個類內部的類叫做巢狀類。

巢狀類是個獨立的類,與外層類基本沒有關係。巢狀類不包含外部類定義的成員。外層類不包含巢狀類的成員。

巢狀類名在外層類作用域內可見,外層類之外不可見。
巢狀類成員種類與普通類一樣。
巢狀類在其外層類中定義了一個型別成員。

在外層類之外可以定義一個巢狀類,但是宣告必須在外層類之內。

巢狀類的靜態成員定義,在類外使用巢狀的作用域。

巢狀類是外層類內部的一個作用域,因此可以像普通巢狀作用域一樣進行名字查詢。

巢狀類和外層類的物件是相互獨立的。

19.6 union:一種節省空間的類

union是一種特殊的類。可以有多個數據成員,但任意時刻只有一個數據成員有值。union也定義了一種型別。

union成員不能是引用型別。預設成員是公有型別。

union可以定義成員函式,但不能繼承和派生,因此不能有虛擬函式。

union預設是未初始化的,可以使用一個花括號的初始值來初始化union物件。

為union的一個成員賦值會令其它成員變成未定義的狀態。因此使用時必須清楚知道其中儲存的型別。

匿名union沒有名字,只能有公有資料成員,在定義所在的作用域可以直接呼叫成員名。

含有類型別成員的union需要在賦值時呼叫型別成員的建構函式與解構函式。

一般對於union來說,包含了類型別的成員的情況構造或銷燬的操作很複雜,一般使用一個類來管理。比如書上的Token類例子,例子中使用一個enum列舉型別來追蹤union中儲存的值型別。

union中類成員無法自動銷燬。為union中類成員賦值時使用定位new操作。

19.7 區域性類

類可以定義在某個函式的內部,這種叫做區域性類。區域性類不允許靜態成員。

區域性類只能訪問外層作用域定義的型別名、靜態變數以及列舉成員。普通區域性變數不能被使用。

區域性類的名字查詢與普通作用域相似。

19.8 固有的不可移植的特性

C++中有些因機器而異的特性叫做不可移植特性。

19.8.1 位域

類可以將其非靜態資料成員定義為位域。位域的型別必須是整型或列舉型別。通常情況使用一個無符號型別儲存一個位域。

取地址運算子不能用於位域。

19.8.2 volatile限定符

當物件的值可能在程式的控制或檢測之外被改變時,可以宣告為volatile。

volatile與const不矛盾。只有volatile的成員函式才能被volatile的物件呼叫。

合成的拷貝/移動建構函式以及賦值運算子不能為volatile物件初始化和賦值。

19.8.3 連結指示:extern ”C“

C++可以呼叫其他語言編寫的函式,如C語言。其它語言編寫的函式也要在C++中宣告。使用連結指示來指出非C++語言。

宣告一個非C++的函式:

extern ”C" size_t strlen(const char*);
extern "C" {
int strcmp(const char*, const char*);
char* strcat(char*, const char*);
}

連結指示可以直接用花括號包含一個頭檔案,表示這個標頭檔案所有函式宣告都是其它語言。

extern “C” void(*pf)(int);是一個 指向C語言函式的指標。指向C函式的指標與指向C++函式的指標是兩種型別的變數。

連結指示對生命中的返回型別或形參型別都有效。

也可以通過使用連結指示對函式進行定義,使一個C++函式可以在其它語言中使用。但是需要注意返回型別和形參型別的限制。

C語言不支援過載,因此連結指示為C語言的不能夠說明同名函式。