美文网首页
Boolan/C++面向对象高级编程 part5

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

作者: 我才是helo | 来源:发表于2017-11-19 16:17 被阅读0次

    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。


    相关文章

      网友评论

          本文标题:Boolan/C++面向对象高级编程 part5

          本文链接:https://www.haomeiwen.com/subject/ouqpvxtx.html