Boolan/C++面向对象高级编程 part5

C++面向对象高级编程 part5

2017-11-14 11:59:35 / herohlc


item1. 对象模型:vptr与vtbl

1. 类对象内存模型

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;  
};
1511072519130.png
  1. 注意图中的类对象模型,类对象中仅包含成员数据/vptr,不包括函数地址。
  2. deriverd类数据成员可以与base类数据成员重名,两者保存在不同的区域。
  3. base类有虚函数,base类就会有vptr,base类对象有vptr则子类一定有vptr。

扩展:dervied class 调用base class 函数/函数覆盖

#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
  1. derived 类继承了base类的函数调用权,所以可以通过derived对象调用base类的接口。
  2. derived 类的与base类的同名接口,derived会覆盖base类。

注意⚠️:C++继承都继承了哪些东西

  1. 数据
  2. 函数调用权。不是继承函数相关的内存

2. vptr vs. vtbl

base类有虚函数,base类就会有vptr,base类对象有vptr则derived类一定有vptr。

虚函数的继承方式

  1. dervied 类如果不override base类的虚函数,则直接继承base类的虚函数
  2. derived 类如果override base类的虚函数,则在虚表中替换base类的虚函数地址
  3. 多重继承中,虚函数不会“跳着”间接继承,而是继承自己的base类的虚函数。上图中c直接继承b的虚函数,而不是直接继承自a的虚函数。

继承关系下生成的函数

关注上图中的a/b/c 三个类中生成的所有的函数,分成虚函数,非虚函数两种类型。

vtbl

3. 静态绑定 vs. 动态绑定

静态绑定的函数调用方式

函数调用时,执行call xxx(地址)

动态绑定的函数调用方式

通过指针找到对象的vtbl,然后找到正确的函数地址
解释成代码形式如下:
(*(p->vptr)[n])(p)

(*(p->vptr)[n])(p)中的n与虚函数的声明顺序一致

4. 多态的示例

1511075056197.png

多态解决的问题

1. 如何用一个只能容纳一种元素类型的容器,存储不同类型的元素?

容器保存的元素类型为,具有继承关系的base类指针,其指向对象为dervied类对象。

2. 如何让容器中的元素(base类指针),调用相同的接口却具有不同的行为?

接口为虚函数。

多态的条件= up-cast pointer+ virtual function


item2. 对象模型:this

1. this指针参与到多态中

1511075265450.png
  1. 利用多态机制,在base类的成员函数流程中实现通用的逻辑,base类提供通用的接口,通用接口的实现可以迟后由derived 类实现。
  2. 成员函数通常是通过对象调用(static成员函数除外),所以在调用函数时编译器会知道哪个对象在调用函数,此时当前对象的指针会传递给该成员this指针。

注意⚠️:

  1. 编译器在执行虚函数的调用(通过指针的方式)时,是通过指针指向内存单元的vptr找到vtbl中的函数地址,最后去调用地址中的函数。从实际的底层实现机制更容易解释多态语法。

item3. 对象模型:dynamic binding

1. 从汇编的角度解释dynamic binding

1511075345112.png
  1. 上图中a.vfunc1()的调用为static binding。
  2. static binding的汇编执行形式:call xxxx
1511075435047.png
  1. dynamic binding 汇编执行等价于 c的形式(*(p->vptr)[n])(p))

注意⚠️
用对象(非指针)的方式调用成员函数,不会造成多态。


item4. const

1. const member function

注意⚠️
const member function中const修饰成员函数的形式只能用在成员函数中,不能用在全局的函数中。理解:这里的const是用来修饰this指针的,全局函数没有this指针。

2. const obj vs. non-const obj vs. const member function vs. no-const member function

const member function中的const的作用,承诺该成员函数不会修改对象内容。本质是将this指针声明为const*const形式。

xx const object non-const object
const member function v v
non-const member function x v

在设计类接口时要考虑const

class String{
public:
    print(){}  // bad , non-const print  
}
const String str("hello");
str.print();  // error, 

建议:如果member function 不改变对象数据,应该将该member function 声明为const。否则const object 无法调用该接口。
在设计类的接口时就要确定要不要加const。

引申:函数(全局/class member function)的形参,如果不想改变实参,要将其声明为const &

成员函数的const 和non-const 版本共存时,调用谁?

class string{
charT operator[](size_type pos)const
{/*不考虑copy on write*/}

reference operator[](size_type pos) 
{/* 必须考虑copy on write*/}
}
}

string a;
cout << a[1];  // 调用 non-const operator[] 
                 // non-const operator[] ?
a[1] = 'a';    // 调用 non-const operator[] 
                 // non-const operator[] ?
  1. const 作为函数签名的成分
  2. reference 返回值类型的函数可以作为左值。 因此上面代码中non-const版本中的operator[] 可以作为左值使用,需要考虑copy on write,但const 版本的operator[] 返回值类型为charT 智能是右值不必考虑copy on write。
  3. 函数设计要考虑是否会把函数作为左值使用。

注意⚠️
当成员函数的const和non-const版本同时存在,const object只会调用const版本,non-const object 只会调用non-const版本。


item5. new & delete

1. new /delete 表达式 vs. operator new /delete

  1. new /delete表达式 实现中会分解为多个步骤,其中包括调用operator new/delete。

注意⚠️:

  1. new /delete 表达式 不能被重载,operator new /delete 可以被重载。
  2. operator new /delete 是对内存的操作.operator delete不包含调用析构函数的动作,析构函数在delete 表达式中调用。

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);
}
  1. operator new 需要一个size参数
  2. operator new 不是由用户调用,由编译器在expression new中调用
  3. 注意返回值类型为void*
  4. operator new 只需指定 size
  5. 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 ,在调用expression new 创建类对象时,expression new中将调用类的operator new。

expression new /delete分解过程

1511077397576.png

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[]分解过程

1511077834557.png
  1. 注意构造和析构的调用次数,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[] 需要分配一个保存对象个数的内存单元。


item8. 重载 new(), delete()

class member operator new() - placement new

1. class member placement new

示例
Foo* pf = new(300,'c')Foo

  1. class member 可以重载 operator new,写出多个版本的operator new()
  2. 每个版本的声明必须具有独特的参数列,其中第一个参数必须是size_t,其余参数以new 所指定的placment arguments 为初值。

placement argument:new (…)小括号中的参数。size_t在声明时默认需要定义,调用处不用显示指定,size_t 不是 使用时(…)中的参数。

2. class member operator delete () - placement delete

绝不会被 expression delete 调用,只有当调用 placement new 之后调用的ctor抛出异常才会被调。

  1. class member operator delete () 不是必须定义的,如果定义要与placement new对应。

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)扩充申请量

1511078434634.png

1. why using placement new

如果想在new对象时创建额外超过对象大小的内存,使用placement new 代替 默认的new。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容