C++面向?qū)ο蟾呒?jí)編程 part5
2017-11-14 11:59:35 / herohlc
item1. 對(duì)象模型:vptr與vtbl
1. 類對(duì)象內(nèi)存模型
class A{
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1;
int m_data2;
};
class B :public A{
public:
virtual void vfunc1();
void func2();
private:
int m_data3;
};
class C :public B{
public:
virtual void vfunc1();
void func2();
private:
int m_data1;
int m_data4;
};

- 注意圖中的類對(duì)象模型,類對(duì)象中僅包含成員數(shù)據(jù)/vptr,不包括函數(shù)地址。
- deriverd類數(shù)據(jù)成員可以與base類數(shù)據(jù)成員重名,兩者保存在不同的區(qū)域。
- base類有虛函數(shù),base類就會(huì)有vptr,base類對(duì)象有vptr則子類一定有vptr。
擴(kuò)展:dervied class 調(diào)用base class 函數(shù)/函數(shù)覆蓋
#include <iostream>
using std::cout;
using std::endl;
class A{
public:
virtual void vfunc1(){};
virtual void vfunc2(){};
void func1(){ cout << "A::func1" << endl;};
void func2(){ cout << "A::func2" << endl;};
private:
int m_data1;
int m_data2;
};
class B :public A{
public:
virtual void vfunc1(){};
void func2() {
cout << "B::func2" << endl;
};
private:
int m_data3;
};
class C :public B{
public:
virtual void vfunc1(){};
void func2(){ cout << "C::func2" << endl;};
private:
int m_data1;
int m_data4;
};
int main() {
A a;
B b;
C c;
b.func1();
b.func2();
c.func1();
c.func2();
return 0;
}
A::func1
B::func2
A::func1
C::func2
- derived 類繼承了base類的函數(shù)調(diào)用權(quán),所以可以通過(guò)derived對(duì)象調(diào)用base類的接口。
- derived 類的與base類的同名接口,derived會(huì)覆蓋base類。
注意??:C++繼承都繼承了哪些東西
- 數(shù)據(jù)
- 函數(shù)調(diào)用權(quán)。不是繼承函數(shù)相關(guān)的內(nèi)存
2. vptr vs. vtbl
base類有虛函數(shù),base類就會(huì)有vptr,base類對(duì)象有vptr則derived類一定有vptr。
虛函數(shù)的繼承方式
- dervied 類如果不override base類的虛函數(shù),則直接繼承base類的虛函數(shù)
- derived 類如果override base類的虛函數(shù),則在虛表中替換base類的虛函數(shù)地址
- 多重繼承中,虛函數(shù)不會(huì)“跳著”間接繼承,而是繼承自己的base類的虛函數(shù)。上圖中c直接繼承b的虛函數(shù),而不是直接繼承自a的虛函數(shù)。
繼承關(guān)系下生成的函數(shù)
關(guān)注上圖中的a/b/c 三個(gè)類中生成的所有的函數(shù),分成虛函數(shù),非虛函數(shù)兩種類型。
vtbl
3. 靜態(tài)綁定 vs. 動(dòng)態(tài)綁定
靜態(tài)綁定的函數(shù)調(diào)用方式
函數(shù)調(diào)用時(shí),執(zhí)行call xxx(地址)。
動(dòng)態(tài)綁定的函數(shù)調(diào)用方式
通過(guò)指針找到對(duì)象的vtbl,然后找到正確的函數(shù)地址。
解釋成代碼形式如下:
(*(p->vptr)[n])(p)
(*(p->vptr)[n])(p)中的n與虛函數(shù)的聲明順序一致
4. 多態(tài)的示例

多態(tài)解決的問(wèn)題
1. 如何用一個(gè)只能容納一種元素類型的容器,存儲(chǔ)不同類型的元素?
容器保存的元素類型為,具有繼承關(guān)系的base類指針,其指向?qū)ο鬄閐ervied類對(duì)象。
2. 如何讓容器中的元素(base類指針),調(diào)用相同的接口卻具有不同的行為?
接口為虛函數(shù)。
多態(tài)的條件= up-cast pointer+ virtual function
item2. 對(duì)象模型:this
1. this指針參與到多態(tài)中

- 利用多態(tài)機(jī)制,在base類的成員函數(shù)流程中實(shí)現(xiàn)通用的邏輯,base類提供通用的接口,通用接口的實(shí)現(xiàn)可以遲后由derived 類實(shí)現(xiàn)。
- 成員函數(shù)通常是通過(guò)對(duì)象調(diào)用(static成員函數(shù)除外),所以在調(diào)用函數(shù)時(shí)編譯器會(huì)知道哪個(gè)對(duì)象在調(diào)用函數(shù),此時(shí)當(dāng)前對(duì)象的指針會(huì)傳遞給該成員this指針。
注意??:
- 編譯器在執(zhí)行虛函數(shù)的調(diào)用(通過(guò)指針的方式)時(shí),是通過(guò)指針指向內(nèi)存單元的vptr找到vtbl中的函數(shù)地址,最后去調(diào)用地址中的函數(shù)。從實(shí)際的底層實(shí)現(xiàn)機(jī)制更容易解釋多態(tài)語(yǔ)法。
item3. 對(duì)象模型:dynamic binding
1. 從匯編的角度解釋dynamic binding

- 上圖中
a.vfunc1()的調(diào)用為static binding。 - static binding的匯編執(zhí)行形式:
call xxxx

- dynamic binding 匯編執(zhí)行等價(jià)于 c的形式
(*(p->vptr)[n])(p))
注意??
用對(duì)象(非指針)的方式調(diào)用成員函數(shù),不會(huì)造成多態(tài)。
item4. const
1. const member function
注意??
const member function中const修飾成員函數(shù)的形式只能用在成員函數(shù)中,不能用在全局的函數(shù)中。理解:這里的const是用來(lái)修飾this指針的,全局函數(shù)沒(méi)有this指針。
2. const obj vs. non-const obj vs. const member function vs. no-const member function
const member function中的const的作用,承諾該成員函數(shù)不會(huì)修改對(duì)象內(nèi)容。本質(zhì)是將this指針聲明為const*const形式。
| xx | const object | non-const object |
|---|---|---|
| const member function | v | v |
| non-const member function | x | v |
在設(shè)計(jì)類接口時(shí)要考慮const
class String{
public:
print(){} // bad , non-const print
}
const String str("hello");
str.print(); // error,
建議:如果member function 不改變對(duì)象數(shù)據(jù),應(yīng)該將該member function 聲明為const。否則const object 無(wú)法調(diào)用該接口。
在設(shè)計(jì)類的接口時(shí)就要確定要不要加const。
引申:函數(shù)(全局/class member function)的形參,如果不想改變實(shí)參,要將其聲明為const &
成員函數(shù)的const 和non-const 版本共存時(shí),調(diào)用誰(shuí)?
class string{
charT operator[](size_type pos)const
{/*不考慮copy on write*/}
reference operator[](size_type pos)
{/* 必須考慮copy on write*/}
}
}
string a;
cout << a[1]; // 調(diào)用 non-const operator[]
// non-const operator[] ?
a[1] = 'a'; // 調(diào)用 non-const operator[]
// non-const operator[] ?
- const 作為函數(shù)簽名的成分
- reference 返回值類型的函數(shù)可以作為左值。 因此上面代碼中non-const版本中的operator[] 可以作為左值使用,需要考慮copy on write,但const 版本的operator[] 返回值類型為charT 智能是右值不必考慮copy on write。
- 函數(shù)設(shè)計(jì)要考慮是否會(huì)把函數(shù)作為左值使用。
注意??
當(dāng)成員函數(shù)的const和non-const版本同時(shí)存在,const object只會(huì)調(diào)用const版本,non-const object 只會(huì)調(diào)用non-const版本。
item5. new & delete
1. new /delete 表達(dá)式 vs. operator new /delete
- new /delete表達(dá)式 實(shí)現(xiàn)中會(huì)分解為多個(gè)步驟,其中包括調(diào)用operator new/delete。
注意??:
- new /delete 表達(dá)式 不能被重載,operator new /delete 可以被重載。
- operator new /delete 是對(duì)內(nèi)存的操作.operator delete不包含調(diào)用析構(gòu)函數(shù)的動(dòng)作,析構(gòu)函數(shù)在delete 表達(dá)式中調(diào)用。
item6. 重載operatro new /delete
1. 重載全局operator new/delete
inline void* operator new(size_t size){
cout << "my new :" <<size<< endl;
return malloc(size);
}
inline void* operator new[](size_t size) {
cout << "my new [] : " << size<< endl;
return malloc(size);
}
inline void operator delete(void* ptr){
cout << "my delete" << endl;
free(ptr);
}
inline void operator delete[](void * ptr) {
cout << "my delete[]" << endl;
free(ptr);
}
- operator new 需要一個(gè)size參數(shù)
- operator new 不是由用戶調(diào)用,由編譯器在expression new中調(diào)用
- 注意返回值類型為void*
- operator new 只需指定 size
- operator delete需要指定地址和可選的size
2. 重載member operator new/delete
class Foo {
public:
void*operator new(size_t size) {
cout << "Foo new" << endl;
return malloc(size);
}
void operator delete(void* ptr){
cout << "Foo delete" << endl;
free(ptr);
}
};
int main() {
Foo* f = new Foo;
delete f;
return 0;
}
Foo new
Foo delete
類如果重載operator new/delete ,在調(diào)用expression new 創(chuàng)建類對(duì)象時(shí),expression new中將調(diào)用類的operator new。
expression new /delete分解過(guò)程

3. 重載member operator new[]/delete []
class Foo {
public:
void*operator new[](size_t size) {
cout << "Foo new [] : " << size <<endl;
return malloc(size);
}
void operator delete[](void* ptr){
cout << "Foo delete[] : " << ptr << endl;
free(ptr);
}
};
expression new[] /delete[]分解過(guò)程

- 注意構(gòu)造和析構(gòu)的調(diào)用次數(shù),operator new中傳入的size值。
item7. 示例
class Foo {
public:
void* operator new(size_t size) {
cout << "Foo new" << endl;
return malloc(size);
}
void operator delete(void* ptr){
cout << "Foo delete" << endl;
free(ptr);
}
void*operator new[](size_t size) {
cout << "Foo new [] : " << size <<endl;
return malloc(size);
}
void operator delete[](void* ptr, size_t size){
cout << "Foo delete[] : " << ptr << endl;
free(ptr);
}
private:
int _id;
long _data;
string _str;
};
1. 如何使用全局的operator new / delete
Foo * p = ::new Foo;
::delete p
2. operator new 傳入的size 大小
operator new[] 需要分配一個(gè)保存對(duì)象個(gè)數(shù)的內(nèi)存單元。
item8. 重載 new(), delete()
class member operator new() - placement new
1. class member placement new
示例
Foo* pf = new(300,'c')Foo
- class member 可以重載 operator new,寫出多個(gè)版本的operator new()
- 每個(gè)版本的聲明必須具有獨(dú)特的參數(shù)列,其中第一個(gè)參數(shù)必須是size_t,其余參數(shù)以new 所指定的placment arguments 為初值。
placement argument:new (…)小括號(hào)中的參數(shù)。size_t在聲明時(shí)默認(rèn)需要定義,調(diào)用處不用顯示指定,size_t 不是 使用時(shí)(…)中的參數(shù)。
2. class member operator delete () - placement delete
絕不會(huì)被 expression delete 調(diào)用,只有當(dāng)調(diào)用 placement new 之后調(diào)用的ctor拋出異常才會(huì)被調(diào)。
- class member operator delete () 不是必須定義的,如果定義要與placement new對(duì)應(yīng)。
3. 示例
class Foo {
public:
Foo(){};
Foo(int a){
throw a;
};
void* operator new(size_t size, int extra){
return malloc(size+extra);
}
void operator delete(void* ptr, int extra){
cout << "placement delete called" << endl;
}
private:
int _id;
long _data;
string _str;
};
int main() {
Foo* a = new(1)Foo;
Foo* b = new(1)Foo(1);
return 0;
}
item9. basic_string使用new(extra)擴(kuò)充申請(qǐng)量

1. why using placement new
如果想在new對(duì)象時(shí)創(chuàng)建額外超過(guò)對(duì)象大小的內(nèi)存,使用placement new 代替 默認(rèn)的new。