美文网首页
成员函数指针与vcall再谈

成员函数指针与vcall再谈

作者: 404Not_Found | 来源:发表于2021-09-11 12:57 被阅读0次
    • 作者: 雪山肥鱼
    • 时间:20210912 12:32
    • 目的: 了解成员函数指针,vcall再谈

    成员函数地址,编译时就确定好了,但是调用成员函数,是需要通过对象调用的。
    所有常规(非静态)成员函数,都需要一个对象进行

    成员函数指针

    直接上代码

        class A
        {
        public:
            void myfunc1(int tmpvalue1)
            {
                cout << "tmpvalue1 = " << tmpvalue1 << endl;
            }
            void myfunc2(int tmpvalue2)
            {
                cout << "tmpvalue2 = " << tmpvalue2 << endl;
            }
    
            static void mysfunc(int tmpvalue)
            {
                cout << "static::tmpvalue = " << tmpvalue << endl;
            }
        };
    
        void fun()
        {
            A mya;
            void (A::*pmypoint)(int tmpvalue) = &A::myfunc1;//定义一个成员函数指针并给初值,成员函数指针!,成员函数需要 this指针
            pmypoint = &A::myfunc2;//给成员函数指针赋值
    
            (mya.*pmypoint)(15);//通过成员函数指针来调用成员函数,必须通过对象
    
            A *pmya = new A();
            (pmya->*pmypoint)(20);
    
            //编译器视角
            //pmypoint(&mya, 15);
            //pmypoint(pmya, 15);
        
            //针对static 则需要普通的函数指针即可
            void(*pmyspoint)(int tmpvalue) = &A::mysfunc;
            pmyspoint(80);
    
        }
    

    注意上述成员函数指针的基础用法。
    然而对于 static 静态成员函数,则需要普通的 函数指针即可。

    成员函数指针的应用

    基本应用: 来源于网络

    class A
    {
      public:
        void strcpy( char *, const char *);
        void strcat( char *, const char *);
    }
    
    typedef void (A::*PMA) (char *, const char *);
    PMA pmf = &A::strcat;
    
    void dispather(A a, PMA p)
    {
      char str[4] = { 0 };
      (a. *p)(str, "abc");
    }
    
    void dispather(A *pa, PMA p)
    {
      char str[4] = { 0 };
      (pa-> *p)(str, "abc");
    }
    
    int main(int argc,char **argv)
    {
      A a1;
      PMA p1 = &A::strcpy;
      dispather( a1, p1);
      dispather( &a1, p1);
      return 0;
    }
    

    从基础用法来看,好像没什么用,直接用对象掉函数不就行了?
    此种用法多见于 桌面开发的菜单选项。
    示例1: 来源网络

    enum MENU_OPTIONS {COPY, CONCAT};
    PMA pmf[2] = {&A::strcpy, &A::strcat};
    int main( int argc,char **argv)
    {
      MENU_OPTIONS option; char str[4];
      swtich(option)
      {
        case COPY:
          (pa ->*pmf[COPY])(str, "abc");
          break;
        case CONCAT:
          (pa->*pmf[CONCAT])(str, "abc");
          break;
        ...
      }
    }
    

    示例2:函数表驱动,来源网络

    #include <iostream>
    #include <string>
    #include <map>
    using namspace std;
    class A;
    typedef int (A::*pClassFun)(int, int);
    
    class A {
    public:
      A() {
        table["+"] = &A::add;
        table["-"] = &A::mns; 
        table["*"] = &A::mul; 
        table["/"] = &A::dev; 
      }
      int add(int m, int n){ 
        cout << m << " + " << n << " = " << m+n << endl; 
        return m+n; 
      } 
      int mns(int m, int n){ 
        cout << m << " - " << n << " = " << m-n << endl; 
        return m-n; 
      } 
      int mul(int m, int n){ 
        cout << m << " * " << n << " = " << m*n << endl; 
        return m*n; 
      } 
      int dev(int m, int n){ 
        cout << m << " / " << n << " = " << m/n << endl; 
        return m/n; 
    
    int call(string s, int m, int n) {
      return (*table[s]) (m, n);
    }
    private:
      map<string, pClassFun> table;
    };
    
    int main(int argc,char **argv) {
      A a;
      a.call("+", 8, 2);
      a.call("-", 8, 2); 
      a.call("*", 8, 2); 
      a.call("/", 8, 2); 
      return 0;
    }
    

    上述举例的成员函数指针的应用,像是 对 类内 返回值和参数相同的 同类函数的收纳。

    再谈vcall

        class A
        {
        public:
            void myfunc1(int tmpvalue1)
            {
                cout << "tmpvalue1 = " << tmpvalue1 << endl;
            }
            void myfunc2(int tmpvalue2)
            {
                cout << "tmpvalue2 = " << tmpvalue2 << endl;
            }
    
            static void mysfunc(int tmpvalue)
            {
                cout << "static::tmpvalue = " << tmpvalue << endl;
            }
            virtual void myvirfun(int tmpvalue)
            {
                cout << "tmpvalue in virtual func = " << tmpvalue << endl;
            }
        };
    
        void fun()
        {
            //只要涉及this指针,都需要用成员函数
            void(A::*pmyvirfun)(int tmpvalue) = &A::myvirfun;//需要用成员函数指针去接
    
            A *pvobj = new A();
            (pvobj->*pmyvirfun)(190);//pvobj确认了虚函数表,再用vcall 在虚函数表里进行偏移查找
            delete pvobj;
        }
    }
    

    vcall(vcall trunk) = virtual call 虚函数的调度
    代表一段执行代码的地址,这段代码引导我们去寻找正确的虚函数。
    简单粗暴把vcall 看成虚函数表,vcall[0] 代表第一个虚函数,vcall[4]达标第二个虚函数

    &A::myvirfun,打印出来的是一个地址,这个地址中有一段代码,这个代码记录虚函数表中的偏移值,vcall[0],vcall[4],有了偏移值,再有了对象指针。我们就知道调用的是哪张虚函数表里的哪个函数。
    &A::myvirfun 需要用成员函数指针去接
    再用具体的对象or 对象指针进行调用。

    vcall 在继承中的体现

    namespace _nmsp3
    {
        //vcall 在继承中的体现
        class A
        {
        public:
            void myfunc1(int tmpvalue1)
            {
                cout << "tmpvalue1 = " << tmpvalue1 << endl;
            }
            void myfunc2(int tmpvalue2)
            {
                cout << "tmpvalue2 = " << tmpvalue2 << endl;
            }
    
            static void mysfunc(int tmpvalue)
            {
                cout << "static::tmpvalue = " << tmpvalue << endl;
            }
            virtual void myvirfun(int tmpvalue)
            {
                cout << "tmpvalue in A = " << tmpvalue << endl;
            }
            virtual ~A()
            {
    
            }
        };
    
        class B:public A
        {
        public:
            virtual void myvirfun(int tmpvalue)
            {
                cout << "tmpvalue in B = " << tmpvalue << endl;
            }
        };
    
        void fun()
        {
            B *pmyb = new B();
            void(B::*mybvirfun)(int tempvalue) = &A::myvirfun;
    //vcall 里的偏移 不管是在A 类 还是 B 类,myvirfun 都在虚函数表的第一个即 vcall[0]
    //调的是 B对象指针pmyb, 也就是说调用 的是 B 的虚函数表, 偏移是 vcall[0] 的虚函数
    //&A::myvirfun 和 &B::myvirfun 地址不同,但是因为偏移是相同的,用的vcall[0] 
            //执行的还是B 
            (pmyb->*mybvirfun)(180);
        }
    }
    

    理解注释:

    1. B 对象,确认了 this指针,确认了哪张虚函数表
    2. &A::myvirfun,vcall[0] 确认了偏移
    3. 调用
      PS: &A::myvirfun 和 &B::myvirfun 的地址不同,但是偏移是相同的 ,都是vcall[0]。所以最后执行的依旧是子类的函数

    inline

    顺便谈一下inline吧。
    inline 有效果的意思是,在编译时就会展开成汇编代码。inline 无效时,直接就成了成员函数的调用。区分这一点就可以了。编译器会自己决定,inline是否值得被优化。

    相关文章

      网友评论

          本文标题:成员函数指针与vcall再谈

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