美文网首页
成员函数指针与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