c++ primer 隨筆(7) 特殊工具與技術

優化內存分配

c++提供兩種方法分配和釋放未構造的原始內存:
1.allocator類
2.標準庫中的new和delete操作符
應當引起注意的是,這裏的內存分配不是分配內存給對象的實體,而是分配一個未使用的空間,此後在這片空間上的操作,都由allocator類給出,這樣做實際上實現了內存空間分配與對象構建的分離。

使用步驟:
由於allocator將內存空間的分配和對象的構建分離,故使用allocator分爲以下幾步
1.allocator與類綁定,因爲allocator是一個泛型類
2.allocate()申請指定大小空間
3.construct()構建對象,其參數爲可變參數,所以可以選擇匹配的構造函數
4.使用,與其它指針使用無異
5.destroy()析構對象,此時空間還是可以使用
6.deallocate()回收空間

allocator<T> a;
a.allocate(n);//分配原始未構造內存保存n個T對象
a.deallocate(p,n);//釋放內存,在名爲p的T*指針中包含的地址處保存T類型的n個對象
a.construct(p,t);//在T*指針p所指內存中構造一個新元素,使用複製構造函數用t初始化該對象
a.destory(p);//運行T*指針p所指對象的析構函數

new函數與delete函數

當new函數運行時,實際上發生三個步驟:
1.調用operator new的標準庫函數,分配足夠大的原始未類型化的內存,以保存指定類型的一個對象。
2.運行該類型的一個構造函數,使用指定初始化式構造對象
3.返回指向新分配並構造對象的指針
當delete函數運行時,實際上發生兩個步驟:
1.對指針指向的對象調用適當的析構函數
2.調用operator delete的標準庫函數釋放對象使用的內存

定位new表達式

標準庫函數operator new和operator delete是allocator的allocate和deallocate成員的低級版本,他們都分配但不初始化內存。
allocator的成員construct和destroy也有兩個低級選擇,這些成員在由allocator對象分配的空間中初始化和撤銷對象。
類似construct成員,有第三種new表達式,稱爲定位new
定位new不分配內存,接受指向已分配但未構造內存的指針,在該內存中初始化一個對象。它能讓我們在特定的,預分配的內存地址構造一個對象。
示例:
new (ptr) string(str); //在指針ptr空間內構造對象string,初始化爲str的複製構造

顯式析構函數的調用

析構函數的顯式調用是destroy函數的低級版本
它只清除對象本身,而不釋放其所佔的內存。
而operator delete函數不會運行析構函數,它只釋放指定的內存。

屬於類的new與delete

編譯器看到類類型的new或delete表達式時,它查看該類是否有operator new或operator delete成員,如果類定義了自己的成員new和delete函數,則使用那些函數爲對象分配和釋放內存,否則調用這些函數的標準庫版本。
對於成員new和delete函數,如果類定義了二者其中的一個,則它有義務定義另一個。

成員new和delete

·類成員operator new函數必須具有返回類型void*(任意類型的指針)並接受size_t類型的形參。
·類成員operator delete函數必須具有返回類型void,接受void*和size_t類型的兩個形參。
·一般來說,除非類是某繼承層次的一部分,否則形參size_t不是必須的。
·當delete指向繼承層次中類型的指針時,指針可以指向基類對象,也可以指向派生類對象,這意味着它必須支持virtual的基類析構函數調用派生類的析構函數。
·new和delete函數隱式的爲靜態函數,不必顯式地將其聲明爲static(雖然合法)。它們必須是靜態函數的原因是:它們的使用總是在對象構造之前,或對象撤銷之後,因此這些函數沒有成員數據可以操縱,只能直接訪問所屬類的靜態成員。

覆蓋類特定的內存分配

string *p=: :new string;
: :delete p;
這樣不管該類是否聲明瞭new和delete函數,它們都調用全局函數operator new與operator delete。

RTTI(運行時類型識別)

c++通過下面兩種方式實現RTTI:
1.typeid操作符,返回指針或引用所指對象的實際類型。
2.dynamic_cast操作符,將基類類型的指針或引用安全的轉換爲派生類型的指針或引用。
對於帶虛函數的類,在運行時執行RTTI操作符,但對於其他類型,在編譯時執行RTTI操作符。

dynamic_cast操作符

可以使用該操作符將基類對象的引用或指針轉換爲同一繼承層次中其他類型的引用或指針。
若指針轉換失敗,則dynamic_cast的結果爲0;
若引用轉換失敗,則拋出一個bad_cast類型的異常。
一般來說,引用或指針綁定的對象的類型在編譯時是未知的,基類的指針可以賦值爲指向派生類對象,同樣,基類的引用也可以用派生類對象初始化,因此,dynamic_cast操作符執行的驗證必須在運行時進行。
使用示例:
1.指針
if(Derived *derivedPtr=dynamic_cast<Derived*>(basePtr)) //將指向基類的指針在運行時轉爲派生類
{
//這裏該指針操作的是派生類對象
}
else
{
//這裏該指針操作的是基類對象
}

2.引用
void f(const Base &b)
{
try{
const Derived &d=dynamic_cast<const Derived&>(b);
//b是基類類型的對象,將其轉換成了想要的派生類對象的引用
} catch(bad_cast)
{
//處理異常
}
}

typeid操作符

typeid將返回該類的類型
使用示例:
Based *bp;
Derived *dp;
if(typeid(*bp)==typeid(*dp))
{

}
else if(typeid(*bp)==typeid(Based))
{

}
typeid神奇的機制是:
當使用基類指針指向子類成員時,如果該基類中有虛函數,則運行時檢測typeid將會返回具體的派生類類型;若基類中不含有虛函數,則運行時typeid將會返回基類類型。
常見於各類定義的equal函數或重載的==運算符函數中,這樣帶來的好處是,不必對每一個類都寫一個比較類型的函數。
如若要顯式打印出對象的具體類型,可用type_info類的name()函數。
例如:
cout<<typeid(*ptr).name()<<endl;

類成員的指針

成員指針只應用於類的非static成員,由於static成員不是任何對象的組成部分,所以不需要特殊的語法來指向static成員,static成員指針是普通指針。
示例:
class A
{
private:
string str;
int num;
public:
const char f();
const char g(int a,string b);
}

string A: : *ptr; //指向A類下string成員的指針
int A: : *a; //指向A類下int成員的指針

指向成員函數的指針

它需要指明下面的信息
1.函數形參的類型和數目,包括成員是否爲const
2.返回類型
3.所屬類的類型
示例:
char (A: : *ptr) ( ) const
char (A: : *ptr) ( ) const =&A: :f ;
char (A: : *ptr) ( int ,string ) const =&A: :f ;

const_cast

有時一個成員函數在邏輯上是const,在該函數中不應修改任何成員值,但有時會需要初始賦值,此時由於函數被聲明爲const限定,程序不允許其修改該類的成員值,導致問題。

解決方法:

使用const_cast強制將該const函數指針賦給一個非const函數指針,使用該函數指針完成需要做的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章