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

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

#include 
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. 多态的示例

Boolan/C++面向对象高级编程 part5_第2张图片
1511075056197.png

多态解决的问题

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

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

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

接口为虚函数。

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


item2. 对象模型:this

1. this指针参与到多态中

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

注意⚠️:

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

item3. 对象模型:dynamic binding

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

Boolan/C++面向对象高级编程 part5_第4张图片
1511075345112.png
  1. 上图中a.vfunc1()的调用为static binding。
  2. static binding的汇编执行形式:call xxxx
Boolan/C++面向对象高级编程 part5_第5张图片
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  :" <
  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分解过程

Boolan/C++面向对象高级编程 part5_第6张图片
1511077397576.png

3. 重载member operator new[]/delete []

class Foo {
public:

    void*operator new[](size_t size) {
        cout << "Foo new [] : " << size <

expression new[] /delete[]分解过程

Boolan/C++面向对象高级编程 part5_第7张图片
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 <

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

Boolan/C++面向对象高级编程 part5_第8张图片
1511078434634.png

1. why using placement new

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


你可能感兴趣的:(Boolan/C++面向对象高级编程 part5)