this指针和虚函数理解

this指针

就是对象的首地址.
无虚函数时,即第1个成员变量的地址.
有虚函数,第1个成员变量为修正为vptr,指向vtbl
调用成员函数时,如函数里要使用成员变量,编译器根据this指针的偏移来寻访各成员变量的值.
这就是成员变量or函数都隐含this的含义.

vptr和vtbl

vptr指向虚函数表的指针
在构造对象完成后才产生.vptr将指向一个虚函数表,每个表对应类继承的虚函数实现,如果重写了,则是自己的虚函数实现.
多继承,可能出现多个表,如果自己还有虚函数,则排列在首个vptr表的后面
vtbl则是在编译期间,编译器为有虚函数的类生成.
继承关系复杂了,虚函数多了,虚函数表也好,查表也好,显然都会成为负担.不知业界如何解决的.

小技巧

用gdb查看vtbl.

一个包含多继承的对象

(gdb) p dd

$17 = (Derived_2) {<Base<char>> = {_vptr.Base = 0x4014b0 <vtable for Derived_2+16>, m_b = 4, a = "\000\000\000"}, <Base<int>> = {_vptr.Base = 0x4014d0 <vtable for Derived_2+48>, m_b = 5, a = {0, 255, 0, 0}}, m_d = 6}

查看表:

(gdb) print /a *((void**)0x4014b0)@10

$45 = {0x401136 <Derived_2::fun_virtual()>, 0x401168 <Derived_2::new_fun_virtual()>, 0xfffffffffffffff0, 
  0x401520 <_ZTI9Derived_2>, 0x401161 <_ZThn16_N9Derived_211fun_virtualEv>, 0x0, 0x401568 <_ZTI4BaseIiE>, 
  0x40130c <Base<int>::fun_virtual()>, 0x0, 0x401588 <_ZTI7Derived>}

中间还有些奇奇怪怪的地址不知道是什么.

示例加注释
#include <iostream>
#include <string>
#include <assert.h>


using std::cout;
using std::endl;

template <typename T>
class Base {
    public:
        Base(){};
        Base(int b): m_b(b){};
        ~Base(){};
        void fun_base() {std::cout << "base" << std::endl;};
        virtual void fun_virtual() {std::cout << "base virtual" << std::endl;};
        int getVal() {cout << __func__ << endl; return m_b;};
        void setVal(int val) {m_b = val;};
        void print_this(){ cout << "This: " << this << endl;}
        int m_b;
        T a[4];
};


class Derived : public Base<char> {
    public:
         Derived(){};
         Derived(int b, int d):Base<char>(b), m_d(d) {};
        ~Derived(){};

        void fun_derived() {std::cout << "derived" << std::endl;};
        void fun_virtual() {std::cout << "derived virtual" << std::endl;};
        int getVal() {return m_d;};
        int m_d;
};

class Derived_2 : public Base<char>,public Base<int> {
    public:
         Derived_2(){};
         Derived_2(int cb, int cd, int d):Base<char>(cb),Base<int>(cd),m_d(d) {};
        ~Derived_2(){};

        void fun_derived() {std::cout << "derived_2" << std::endl;};
        void fun_virtual() {std::cout << "derived_2 virtual" << std::endl;};
        int getVal() {return m_d;};
        virtual void new_fun_virtual(){std::cout << "derived_2 virtual" << std::endl;};
        int m_d;
};





int main(int argc, char* argv)
{
    Base<char> b(1);
    Derived d(2,3);
    Derived_2 dd(4,5,6);

    /*
     *定义了虚函数的对象,首元素为vptr
     */
    cout <<  "sizeof  int: " << sizeof(int) << endl;
    //4 + 4 + 8(vptr)
    cout << "sizeof b :"  << sizeof(b) << endl;
    // 4+4+4+8 = 20 再8字节对齐 = 24
    cout << "sizeof d :"  << sizeof(d) << endl;

    /*
     * 成员函数调用通过this偏移寻访其他成员变量
     * 普通函数调用fun(a,b,c,d);将d,c push stack,a b可能直接寄存器
     * 成员函数也是同样的!只不过d,c不明显.
     这里包含了一种面象过程到面向对象转变的深刻思想
     */
    b.fun_base();
    b.fun_virtual();
    std::cout << b.getVal() << std::endl;
    /*
     * bb仍然可以调用Base::func! 只不过push的this不知道是什么东西了
     * */
    Base<char>*  bb =  nullptr;
    bb->fun_base();
    //std::cout << bb->getVal() <<  std::endl;//不能用

    /*
     *将子对象d强制转换为父对象b1,实际是通过拷贝构造函数,因此跟vptr无关,
     不会覆盖它
      *b=d前 内存视图
      *b的内存试图
      *[0x....0] vptrb = ----->
      *[0x....8] m_b = 1
      */
    /*
     *子类有虚函数实现, vptr为指针,占对象首8字节
     *d的内存视图
     *[0x....0] vptrd---------->
     *[0x....8] m_b = 2
     *[0x....c] m_d = 3
     */

     /*
      *b=d后 内存视图
      *b的内存试图
      *[0x....0] vptrb = ----->
      *[0x....8] m_b = 2
     */
    Base<char> b1  = d;
    /*
     * if no虚函数 this 地址= 1st元素首地址 = 对象取地址
     * if has虚函数 this 地址= vptr 地址 = 对象取地址
     * */
    cout << "Addr of d:  " << &d << endl;
    d.print_this();
    cout << "Addr of 1st elem: " <<&d.m_b << endl;

    cout << "Addr of b:  " << &b << endl;
    b.print_this();
    cout << "Addr of 1st elem: " <<&b.m_b << endl;

    //调用成员函数时一定是Base::func,编译器都判断好了
    b1.fun_base();

    //虚函数,从vtbl里找,只有Base::fun_virtual();
    b1.fun_virtual(); 

    //拷贝构造过来的 来自d
    cout << "copy from d: " << b1.getVal() << endl; //该值来自d!

    //Derived d1  = b;//不允许将父类强转为子类

    //初始化
    Base<char>* ptr=  &b; 
    //ptr指向d, 但内存模型仍然为Base<char> 
    ptr=  &d; 
    //非虚函数,Base::func_base();
    ptr->fun_base();
    //但虚函数显然使用的是实际对象d的vptr
    ptr->fun_virtual();

    cout << "from d: " << ptr->getVal() << endl; //该值来自d!

    //引用效果和指针一样
    Base<char> & ref = d;
    cout << "Ref virtual call same with pointer\n";
    ref.fun_virtual();


    //1 多继承,多个vtbl
    //用哪个vtbl,看左边指针是什么原型
    //2 继承者又定义了新的virtual函数,应该合并到第1张vtbl表里,而且排在已有的
    //虚函数后面
    Base<char>* ptr_char=  &dd; 
    ptr_char->fun_virtual();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值