C++ Primer 筆記-第19章 特殊工具與技術(shù)

19.1 控制內(nèi)存分配
19.1.1 重載new
和delete
new一個對象分三個步驟:
(1)new
表達(dá)式調(diào)用名為operator new
(或者operator new[]
)的標(biāo)準(zhǔn)庫函數(shù)。該函數(shù)分配一塊足夠大的、原始的、未命名的內(nèi)存空間以便存儲特定類型的對象(或者對象的數(shù)組)。
(2)編譯器運(yùn)行相應(yīng)的構(gòu)造函數(shù)以構(gòu)造這些對象,并為其傳入初始值。
(3)對象被分配了空間并構(gòu)造完成,返回一個指向該對象的指針。delete
一個對象分兩個步驟:
(1)調(diào)用對象的析構(gòu)函數(shù)。
(2)編譯器調(diào)用名為operator delete
(或者operator delete[]
)的標(biāo)準(zhǔn)庫函數(shù)釋放內(nèi)存空間。如果應(yīng)用程序希望控制內(nèi)存分配的過程,則它們需要定義自己的
operator new
函數(shù)和operator delete
函數(shù)。即使在標(biāo)準(zhǔn)庫中已經(jīng)存在這兩個函數(shù)的定義,我們?nèi)耘f可以定義自己的版本。當(dāng)自定義了全局的
operator new
函數(shù)和operator delete
函數(shù)后,我們就負(fù)擔(dān)起了控制動態(tài)內(nèi)存分配的職責(zé)。這兩個函數(shù)必須是正確的:因為它們是程序整個處理過程中至關(guān)重要的一部分。當(dāng)我們將上述兩個運(yùn)算符函數(shù)定義成類的成員時,它們是隱式靜態(tài)的,也必須是靜態(tài)的。而且它們不能操縱類的任何數(shù)據(jù)成員。
標(biāo)準(zhǔn)庫函數(shù)
operator new
和operator delete
的名字容易讓人誤解。和其他operator
函數(shù)不同(比如operator=
),這兩個函數(shù)并沒有重載new
表達(dá)式和delete
表達(dá)式。實際上,我們根本無法自定義new
表達(dá)式或delete
表達(dá)式的行為。我們提供新的
operator new
函數(shù)和operator delete
函數(shù)的目的在于改變內(nèi)存分配的方式,但是不管怎樣,我們都不能改變new
運(yùn)算符和delete
運(yùn)算符的基本含義。

void* operator new(size_t size){
? ?if(void* mem = malloc(size))
? ? ? ?return mem;
? ?else
? ? ? ?throw bad_alloc();
}
void operator delete(void* mem) noexcept {
? ?free(mem);
}

19.2 運(yùn)行時類型識別
運(yùn)行時類型識別(run-time type identification, RTTI)?的功能由兩個運(yùn)算符實現(xiàn):
(1)type運(yùn)算符,用于返回表達(dá)式的類型。
(2)dynamic_cast?運(yùn)算符,用于將基類的指針或引用安全地轉(zhuǎn)換成派生類的指針或引用。程序員必須清楚地知道轉(zhuǎn)換的目標(biāo)類型并且必須檢查類型轉(zhuǎn)換是否被成功執(zhí)行。
使用RTTI必須要加倍小心。在可能的情況下,最好定義虛函數(shù)而非直接接管類型管理的重任。
19.2.1?dynamic_cast
運(yùn)算符
dynamic_cast
運(yùn)算符的使用形式:dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)
我們可以對一個空指針執(zhí)行
dynamic_cast
,結(jié)果是所需類型的空指針。當(dāng)引用的類型轉(zhuǎn)換失敗時,程序拋出一個名為
std::bad_cast
的異常:

void f(const Base& b) {
? ?try {
? ? ? ?const Derived& d = dynamic_cast<const Derived&>(b);
? ?} catch (bad_cast) {
? ? ? ?// 處理類型轉(zhuǎn)換失敗的情況
? ?}
}

19.2.2?typeid
?運(yùn)算符
typeid
表達(dá)式的形式是typeid(e)
,其中e可以是任意表達(dá)式或類型的名字。type
操作的結(jié)果是一個常量對象的引用,該對象的類型是標(biāo)準(zhǔn)庫類型type_info
或?type_info
的公有派生類型。

Derived* dp = new Derived;
Base* bp = dp;?// 兩個指針都指向Derived對象;
// 在運(yùn)行時比較兩個對象的類型
if(typeid(*dp) == typeid(*bp)) {
? ?// dp 和 bp 指向同一類型的對象
}
//檢查運(yùn)行時類型是否是某種指定的類型
if(typeid(*bp) == typeid(Derived)) {
? ?// bp實際指向Derived對象
}
// 注意,typeid應(yīng)該作用于對象,因此我們使用*bp而非bp
// 下面的檢查永遠(yuǎn)是失敗的:bp的類型是指向Base的指針
if(typeid(bp) == typeid(Derived)) {?
?? ?// 此處的代碼永遠(yuǎn)不會執(zhí)行
}

當(dāng)?
typeid
作用于指針時(而非指針?biāo)傅膶ο螅?,返回的結(jié)果是該指針的靜態(tài)編譯時類型。如果p是一個空指針,則
typeid(*p)
將拋出一個名為bad_typeid
的異常。
19.2.4?type_info
類
創(chuàng)建
type_info
對象的唯一途徑是使用typeid
運(yùn)算符。對于某種給定的類型來說,
name
的返回值因編譯器而異并且不一定與在程序中使用的名字一致。對于name
返回值的唯一要求是,類型不同則返回的字符串必須有所區(qū)別。有的編譯器提供了額外的成員函數(shù)以提供程序中所用類型的額外信息。
19.3 枚舉類型
枚舉類型(enumeration)?使我們可以將一組整型常量組織在一起。和類一樣,每個枚舉類型定義了一種新的類型。枚舉屬于字面值常量類型。
c++包含兩種枚舉:限定作用域和不限定作用域的。
在限定作用域的枚舉類型中,枚舉成員的名字遵循常規(guī)的作用域準(zhǔn)則,并且在枚舉類型的作用域外是不可訪問的。與之相反,在不限定作用域的枚舉類型中,枚舉成員的作用域與枚舉類型本身的作用域相同。
枚舉成員是
const
,因此在初始化枚舉成員時提供的初始值必須是常量表達(dá)式。想要初始化
enum
對象或者為enum
對象賦值,必須使用該類型的一個枚舉成員或者該類型的另一個對象。即使某個整數(shù)值恰好與枚舉成員的值相等,它也不能作為函數(shù)的
enum
實參使用。c++11新標(biāo)準(zhǔn)中,我們可以在
enum
的名字后加上冒號以及我們想在該enum
中使用的類型。c++11新標(biāo)準(zhǔn)中,我們可以提前聲明
enum
。enum
的前置聲明(無論隱式地還是顯示地)必須指定其成員的大小。enum
的聲明和定義必須匹配,這意味著在該enum
的所有聲明和定義中成員的大小必須一致。而且,我們不能在同一個上下文中先聲明一個不限定作用域的enum
名字,然后再聲明一個同名的限定作用域的enum
。
19.4 類成員指針
類成員指針(pointer to member)?是指可以指向類的非靜態(tài)成員的指針。
成員指針的類型囊括了類的類型以及成員的類型。
當(dāng)初始化一個這樣的指針時,我們令其指向類的某個成員,但是不指定該成員所屬的對象;直到使用成員指針時,才提供成員所屬的對象。
19.4.1 數(shù)據(jù)成員指針

class Screen {
private:
? ?std::string contents;
}
// pdata可以指向一個常量(非常量)Screen對象的String對象
const string Screen::*pdata;
// 初始化一個成員指針(或給它賦值)時,需指定它所指的成員。
pdata = &Screen::contents;

當(dāng)我們初始化一個成員指針或為成員指針賦值時,該指針并沒有指向任何數(shù)據(jù)。成員指針指定了成員而非該成員所屬的對象,只有當(dāng)解引用成員指針時我們才提供對象的信息。

Screen myscreen, *pScreen = &myScreen;
// .*解引用pdata以獲得myScreen對象的contents成員
auto s = myScreen.*pdata;
// ->*解引用pdata以獲得pScreen所指對象的contents成員
s= pScreen->*pdata;
// 從概念上來說,這些運(yùn)算符執(zhí)行兩個操作:
// 它們首先解引用成員指針以得到所需的成員
// 然后像成員訪問運(yùn)算符一樣,通過對象(.*)或指針(->*)獲取成員

可以定義一個返回私有成員指針的函數(shù)。

class Screen {
public:
? ?// data()是一個靜態(tài)成員,返回一個成員指針
? ?static const std::string Screen::*data()
? ? ? ?{ return &Screen::contents; }
private:
? ?std::string contents;
}
// 調(diào)用data()
const string Screen::*pdata = Screen::data();?
// 獲取myScreen對象的contents成員
auto s = myScreen.*pdata;

19.4.2 成員函數(shù)指針

class Screen {
public:
? ?char get_cursor() const;
? ?char get(pos ht, pos wd) const;
? ?// ...
}
// 定義成員函數(shù)的指針:
// pmf是一個指針,它可以指向Screen的某個常量成員函數(shù)
// 前提是該函數(shù)不接受任何實參,并且返回一個char
auto pmf = &Screen::get_cursor;
// 也可以先聲明一個指針,令其指向含有兩個形參的get()
char (Screen::*pmf2)(Screen::pos, Screen::pos) const;
pmf2 = &Screen::get;
// 調(diào)用成員函數(shù)指針
Screen myScreen, *pScreen = &myScreen;
char c1 = (pmyScreen->*pmf)();
char c2 = (myScreen.*pmf2)(0, 0);

因為函數(shù)調(diào)用符優(yōu)先級比較高,所以在聲明指向成員函數(shù)的指針并使用這樣的指針進(jìn)行函數(shù)調(diào)用時,括號必不可少:
(C::*p)(parms)
和(obj.*p)(args)
。通過使用類型別名,可以令含有成員指針的代碼更容易讀寫。

// Action 是函數(shù)指針的別名
using Action =
? ?char (Screen::*)(Screen::pos, Screen::pos) const;
// 讓Action函數(shù)指針指向Screen的get(pos,pos)
Action get = &Screen::get;
// 可以定義一個函數(shù)action
// 將指向成員函數(shù)的指針Action作為action函數(shù)的形參類型
Screen& action(Screen& , Action = &Screen::get);
Screen myScreen;
// 等價的調(diào)用:
action(myScreen); ? ? ? ? ? ? ? // 使用默認(rèn)實參
action(myScreen, get); ? ? ? ? ?// 使用我們定義的函數(shù)指針get
action(myScreen, &Screen::get) ?// 顯示地傳入地址

對于普通函數(shù)指針和指向成員函數(shù)的指針來說,一個常見的用法是將其存入一個函數(shù)表當(dāng)中。
19.4.3 將成員函數(shù)用作可調(diào)用對象
因為成員指針不是可調(diào)用對象,所以我們不能直接將一個指向成員函數(shù)的指針傳遞給算法:

vector<string> svec;
// 錯誤,必須使用.*或->*調(diào)用成員指針
auto fp = &string::empty; ? // fp指向string的empty函數(shù)
find_if(svec.begin(), svec.end(), fp);
// 從指向成員函數(shù)的指針獲取可調(diào)用對象的
// 一種方法是使用標(biāo)準(zhǔn)庫模板function:
function<bool (const string&)> fcn = &string::empty;
find_if(svec.begin(), svec.end(), fcn);
// 第二中方法是使用mem_fn
find_if(svec.begin(), svec.end(), mem_fn(&string::empty));
// 第三中辦法是使用bind
find_if(svec.begin(), svec.end(), bind(&string::empty, _1));

19.6?union
:一種節(jié)約空間的類
聯(lián)合(union)?是一種特殊的類。一個
union
可以有多個數(shù)據(jù)成員,但是在任意時刻只有一個數(shù)據(jù)成員可以有值。當(dāng)我們給
union
的某個成員賦值后,該union
的其他成員就變成未定義的狀態(tài)了。union
不能含有引用類型的成員。由于
union
既不能繼承自其他類,也不能作為基類使用,所以在union
中不能含有虛函數(shù)。匿名union?不能包含受保護(hù)的成員或私有成員,也不能定義成員函數(shù)。
當(dāng)我們將
union
的值改為類類型成員對象的值時,必須運(yùn)行該類型的構(gòu)造函數(shù)。反之,當(dāng)我們將類類型成員的值改為一個其他值時,必須運(yùn)行該類型的析構(gòu)函數(shù)。
19.7 局部類
類可以定義在某個函數(shù)的內(nèi)部,我們稱這樣的類為局部類(local class)。
局部類定義的類型只在定義它的作用域內(nèi)可見。
局部類的所有成員(包括函數(shù)在內(nèi))都必須完整定義在類的內(nèi)部。因此,局部類的作用與嵌套類相比相差很遠(yuǎn)。
局部類只能訪問外層作用域定義的類型名、靜態(tài)變量以及枚舉成員。
如果局部類定義在某個函數(shù)內(nèi)部,則該函數(shù)的普通局部便不能被該局部類使用。
可以在局部類的內(nèi)部再嵌套一個類。此時,嵌套類的定義可以出現(xiàn)在局部類之外。不過,嵌套類必須定義在與局部類相同的作用域內(nèi)。
19.8 固有的不可移植的特性
為了支持低層編程,c++定義了一些固有的**不可移植(nonportable)的特性。
該特性是指因機(jī)器而異的特性,當(dāng)我們將含有不可移植特性的程序從一臺機(jī)器轉(zhuǎn)移到另一臺機(jī)器上時,通常需要重新編寫該程序。
19.8.1 位域
類可以將其(非靜態(tài))數(shù)據(jù)成員定義成位域(bit-filed)。
位域在內(nèi)存中的布局是與機(jī)器相關(guān)的。
位域類型必須是整型或枚舉類型。
通常情況下最好將位域設(shè)為無符號類型,存儲在帶符號類型中的位域的行為將因具體實現(xiàn)而定。

typedef unsigned int Bit;
class File {
? ?Bit mode: 2; ? ? ? ?// mode占2位
? ?Bit modified: 1; ? ?// modified占1位
? ?Bit prot_owner: 3; ?// prot_owner占3位
? ?// ...
}

19.8.2?volatile
限定符
volatile
是一種類型限定符,告訴編譯器變量可能在程序的直接控制之外發(fā)生改變。它起到一種標(biāo)示作用,令編譯器不對代碼進(jìn)行優(yōu)化操作。
19.8.3 鏈接指示:extern "C"
c++使用鏈接指示(linkage directive)?指出任意非c++函數(shù)所用的語言。
