C++对象模型探索--05 函数语义学

普通成员函数调用方式

c++语言涉及有一个要求:要求对普通成员函数的调不应该比全局函数效率差
基于这种设计要求,编译器内部实际上是对成员函数的调用转换成了对全局函数的调用
成员函数有独立的内存地址,是跟着类走的,并且成员函数的地址是在编译器的时候就确定好了的

  • 编译器额外增加了一个叫做this的形参指向生成的对象
  • 对于常规成员变量的存取,都是通过this形参来进行

虚成员函数(虚函数)、静态成员函数调用方式

#include 
#include 

class MyClass
{
public:
    int m_i;

    virtual void virt_func()
    {
        printf("virt_func call,this = %p\n", this);
    }

    static void static_func()
    {
        printf("static_func called\n");
    }
};

int main(int argc, char **argv)
{
    MyClass obj;
    obj.virt_func(); // 用对象调用虚函数,就和调用普通成员函数一样
                     // 不需要通过虚函数表
    MyClass *pobj = new MyClass();
    pobj->virt_func(); // 通过虚函数表指针查找虚函数表,通过虚函数表找到虚函数的入库地址,完成对虚函数的调用
                       // (*pobj->vptr[0])(pobj)
                       // vptr:编译器给生成的虚函数表指针,指向虚函数表
                       // [0]:虚函数表中第一项,即virt_func()的地址
                       // 传递一个参数进去,即this
                       // *:得到虚函数地址

    // 静态成员函数调用方式
    obj.static_func();
    pobj->static_func();
    MyClass::static_func();
    // 静态成员函数特性
    // 静态成员函数没有this指针
    // 无法直接存、取类中普通的非静态成员变量
    // 静态成员函数不能在后面使用const也不能设置为virtual
    // 可以用类对象调用
    // 静态成员函数等同于非成员函数,有需要提供回调函数的地方可以将静态成员函数作为回调函数
    return 0;
}

多继承虚函数析构

#include 
#include 
#include 
#include 

class Base1
{
public:
    virtual void f()
    {
        std::cout << "Base1::f()" << std::endl;
    }

    virtual void g()
    {
        std::cout << "Base1::g()" << std::endl;
    }

    virtual void h()
    {
        std::cout << "Base1::h()" << std::endl;
    }
};

class Base2
{
public:
    virtual void b2_func()
    {
        std::cout << "Base2::b2_func()" << std::endl;
    }
};

class Derived : public Base1, public Base2
{
public:
    virtual void i()
    {
        std::cout << "Derived::i()" << std::endl;
    }

    virtual void g()
    {
        std::cout << "Derived::g()" << std::endl;
    }

    void func()
    {
        std::cout << "Derived::func()" << std::endl;
    }
};

int main(int argc, char **argv)
{
    // 多继承下的虚函数
    // 多重继承的复杂性体现再后面这个基类上(Base2)
 
    Base2 *pb2 = new Derived();
    // 编译器视角
    /*
    Derived *tmp = new Derived();
    Base2 *pb2 = (Base2 *)((char *)tmp + sizeof(Base));
    */

   // 如何删除用第二类指针new 出来的继承类对象
   /*
    要删除的实际是整个Derived()对象,需要保证Derived对象的析构函数被正常调用
    执行delete pb2;
    1. 如果Base2没有析构函数,编译器会直接删除以pb2开始的这段内存,系统一定报
       异常,因为这段内存不是new的起始内存
    2. 如果Base2里有一个析构函数,但整个析构函数是普通析构函数(非虚析构函数),
       那么当执行delete pb2时这个析构函数就会被系统调用,但delete任然是以pb2
       开始的这段内存,系统一定报异常,因为这段内存不是new的起始内存。这是因为
       (析构函数如果不是虚函数,编译器会实施静态绑定,静态绑定意味着执行
        delete pb2时,删除的内存开始地址就是pb2的当前位置)
    3. 如果Base2的析构函数是虚析构函数
		~Derived()
			~Base2()
			~Base1()
	4. 凡是涉及到继承的,所有类加虚析构函数
   */
    return 0;
}

多继承第二基类虚函数支持、虚继承带虚函数支持

多重继承第二基类对虚函数支持的影响

子类继承了几个父类,子类就有几个虚函数表
多重继承下,有机制情况,第二个或者后续的基类会对虚函数的支持产生影响

#include 
#include 
#include 
#include 

class Base1
{
public:
    virtual void f()
    {
        std::cout << "Base1::f()" << std::endl;
    }

    virtual void g()
    {
        std::cout << "Base1::g()" << std::endl;
    }

    virtual void h()
    {
        std::cout << "Base1::h()" << std::endl;
    }

    virtual ~Base1()
    {
        std::cout << "Base1::~Base1()" << std::endl;
    }
};

class Base2
{
public:
    virtual void b2_func()
    {
        std::cout << "Base2::b2_func()" << std::endl;
    }

    virtual ~Base2()
    {
        std::cout << "Base2::~Base2()" << std::endl;
    }
};

class Derived : public Base1, public Base2
{
public:
    virtual void i()
    {
        std::cout << "Derived::i()" << std::endl;
    }

    virtual void g()
    {
        std::cout << "Derived::g()" << std::endl;
    }

    void func()
    {
        std::cout << "Derived::func()" << std::endl;
    }

    virtual ~Derived()
    {
        std::cout << "Derived::~Derived()" << std::endl;
    }
};

int main(int argc, char **argv)
{
    // this指针调整的目的是让对象指针正确指向对象的首地址,
    // 从而能正确的调用对象的成员函数或者说正确确定数据成员的存储位置    
    // 1.通过指向第二个基类的指针调用继承类的虚函数
    Base2 *pb2 = new Derived();
    delete pb2; //调用继承类的虚析构函数
    // 2. 一个指向派生类的指针,调用第二个基类种的虚函数
    Derived *pd2 = new Derived();
    pd2->b2_func();
    // 3. 允许虚函数的返回值类型有所变化 

    return 0;
}
虚继承下的虚函数
#include 
#include 
#include 
#include 

class Base
{
public:
    virtual void f()
    {
        std::cout << "Base::f()" << std::endl;
    }

    virtual ~Base()
    {
        std::cout << "Base::~Base()" << std::endl;
    }

    int m_base;
};

class Derived : public virtual Base
{
public:
    virtual ~Derived()
    {
        std::cout << "Derived::~Derived()" << std::endl;
    }

    int m_derive;
};

int main(int argc, char **argv)
{
    printf("sizeof(Base) = %ld\n", sizeof(Base));
    printf("sizeof(Derived) = %ld\n", sizeof(Derived));

    printf("Base::m_base offset = %d\n", &Base::m_base);
    printf("Derived::m_derive offset = %d\n", &Derived::m_derive);

    Derived *pd = new Derived();
    pd->m_derive = 0xdd;

    Base *pb = (Base *)pd;
    pb->m_base = 0xbb;

    return 0;
}

gdb查看

 gdb main
(gdb) set height 0
(gdb) set print pretty on
(gdb) set print vtbl on
(gdb) set print object on
(gdb) b 47
Breakpoint 1 at 0x12f7: file main.cpp, line 47.
(gdb) r
sizeof(Base) = 16
sizeof(Derived) = 32
Base::m_base offset = 8
Derived::m_derive offset = 8

Breakpoint 1, main (argc=1, argv=0x7fffffffdbc8) at main.cpp:47
47          return 0;
(gdb) p pb
$1 = (Derived *) 0x55555556b2c0
(gdb) p pd
$2 = (Derived *) 0x55555556b2c0
(gdb) p *pd
$3 = (Derived) {
  <Base> = {
    _vptr.Base = 0x555555557cf0 <vtable for Derived+72>,
    m_base = 187
  }, 
  members of Derived:
  _vptr.Derived = 0x555555557cc0 <vtable for Derived+24>,
  m_derive = 221
}
(gdb) info vtbl *pd
vtable for 'Base' @ 0x555555557cf0 (subobject @ 0x55555556b2d0):
[0]: 0x555555555372 <Base::f()>
(gdb) x/8xw pd
0x55555556b2c0: 0x55557cc0      0x00005555      0x000000dd      0x00000000
0x55555556b2d0: 0x55557cf0      0x00005555      0x000000bb      0x00000000

由gdb信息可知pd的内存布局为: vptr_derived| m_derive| padding4 | vptr_base | m_base | padding4 |

RTTI运行时类型识别回顾与存储位置介绍

c++运行时类型识别RTTI要求父类中至少有一个虚函数
RTTI就可以再执行期间查询一个多态指针,或者多态引用信息
RTTI能力靠typeid和dynamic_cast运算符来体现

RTTI实现原理
typeid返回的是一个常量对象的引用,这个常量对象的类型一般是type_info(类)

你可能感兴趣的:(C/C++,c++)