首页 > 代码库 > c++ 类内部函数调用虚函数

c++ 类内部函数调用虚函数

做项目的过程中,碰到一个问题。

问题可以抽象为下面的问题:

普通人吃饭拿筷子,小孩吃饭拿勺子。

 

class People {
public:
    void eat() {
        get_util_to_eat();
    }

    virtual void get_util_to_eat() {
        std::cout << "People get chopsticks" << std::endl;
    }
};

class Children : public People {
public:
    void get_util_to_eat() {
        std::cout << "Children get scoop" << std::endl;
    }
};

int main() {
    People* people = new Children();
    people->eat();
    return 0;
}

 

输出结果:

Children get scoop

当然这也符合我们的预期。

因为people不是虚函数,所以上述程序调用的是people中的eat方法,这就涉及到一个之前我一直模糊的概念,在一个类方法中调用虚方法,是如何调用的。

这又涉及到之前不得不说的一个问题:

class A {
public:
    void print() {
        std::cout << "i am A" << std::endl;
    }
};

int main() {
    A* a = NULL;
    a->print();
    return 0;
}

上述代码会输出什么,按照直观的感觉NULL怎么可能调用方法呢,要出core吧。

但是事实上,输出的是:

i am A

调用类函数的时候,c++编译器并不会管该类是否为空,而是将该类的地址当做this指针传到函数中去。

a->print() 时,在编译器中就相当于print(&a)

有因为print中没有用到成员变量的情况,所以自然能很正确的运行。

然后来看下汇编代码就能更理解了。以下是People类内的汇编代码。

21        void eat() {
   0x0000000000400bd2 <+0>:    push   %rbp 
   0x0000000000400bd3 <+1>:    mov    %rsp,%rbp 
   0x0000000000400bd6 <+4>:    sub    $0x10,%rsp 
   0x0000000000400bda <+8>:    mov    %rdi,-0x8(%rbp) //rsp表示第一个参数,也就是类的指针
get_util_to_eat();
   0x0000000000400be9 <+23>:    mov    -0x8(%rbp),%rax //将类指针放入rax寄存器中
   0x0000000000400bed <+27>:    mov    (%rax),%rax //取首地址值,也就是虚表地址
   0x0000000000400bf0 <+30>:    mov    -0x8(%rbp),%rdi //放入rdi中,下次函数调用的时候取参用
   0x0000000000400bf4 <+34>:    mov    (%rax),%rax //取出虚表中函数的地址
   0x0000000000400bf7 <+37>:    callq  *%rax //调用改函数

总结就是,进入类的非静态成员函数时,会默认携带类的指针(this),然后改函数内用到成员变量、成员方法都等同于在前面加了一个this->

 

So 回到最初的那个问题,在People::eat中传入的是Chilren的指针,所以调用 get_util_to_eat 时从虚表中取出了Children::get_util_to_eat方法并进行调用。

c++ 类内部函数调用虚函数