探究 C++ 中多态、虚函数、虚函数表与 this 的关系

最近因为同学的一个问题,我研究了一下 C++ 的多态性。

先讲讲什么是重载(overload)、覆写(override)和多态(polymorphism)。

重载是:同一个方法名,不同入参对应不同函数,实际是不同的方法签名

覆写是:方法名和入参都相同,即方法签名相同,却对应不同函数。一般允许子类继承父类,覆写父类方法。

多态是:通过父类的方法签名,实际访问到了子类覆写的方法,这一现象叫多态。

C++ 支持静态联编的编译时多态,和动态联编的运行时多态。函数联编是指对一个函数的调用,是确定“函数引用的目标函数体”的过程,可以理解为确定 foo() 这里 foo 指向的真实函数体地址的过程。

C++ 的动态联编通过虚函数(virtual method / virtual function)实现。虚函数存在于继承关系中,在基类声明虚函数,子类覆写虚函数,使我们能够通过表面使用基类的该函数,实际访问到子类覆写后的函数。

#include <iostream>
#include <string>

using namespace std;

class A {
public:
    A(){}
    virtual void foo()=0; // 纯虚函数,声明该函数在 A 中不会被实现
    virtual~A(){}
};

class B : public A {
public:
    B() {}
    virtual void foo();
    virtual ~B(){}
};

void B::foo() {
    cout << "Hello, virtual method." << endl;
}

int main() {
    A* b = new B();
    b->foo(); // Hello, virtual method.

    return 0;
}

注意,必须使用指针访问,如果不通过指针访问会发生对象切片(object slicing)。

关于对象切片参见 StackOverflow: Why do virtual functions need to be passed with a pointer and not by value(of the object) ?

C++ 中虚函数是通过虚函数表(virtual table,v-table)来实现的。

每个有虚函数的类有一个虚函数表,包括纯虚函数和派生类中隐式声明的虚函数。虚函数表的入口指针在对象最开始的位置。虚函数表只存储虚函数“函数指针”的地址,不存放普通函数或是构造函数指针的地址。

//TODO 虚函数的内存分析这里不写了,看到前辈总结的已经很好,留个链接:

CSDN: C++虚函数表详细解释及实例分析

看下面的例子:

#include <iostream>
#include <string>

using namespace std;

class A {
public:
    A(){}
    virtual void foo()=0;
    virtual~A(){}
};

class B : public A {
public:
    B() {}
    virtual void foo();
    virtual ~B(){}
};

void B::foo() {
    cout << "Hello, virtual method." << endl;
}

typedef void (*func)(A*);

int main() {
    A* b = new B();

    void*** vtable = (void***)(b);
    cout << "虚函数表的地址 " << table << endl;
    void** func1 = (void**)(*table + 0);
    cout << "第一个虚函数的地址" << func1 << endl;
    func p = (func)(*func1);
    p(); // Hello, virtual method.

    return 0;
}

其实上面的实例有问题,C++ 的对象中 this 的实现是在编译时改写原方法,在首位增加参数 _this,是指针指向对象。

可以理解为(但实际签名不是这样):

void B_foo(B* _this) {
    cout << "Hello, " << _this->val << endl;
}

int main() {
    A* b = new B();
    B_foo(b);

    return 0;
}

所以,当我们需要在虚函数中使用对象变量的时候,我们就要传入第一个参数:

#include <iostream>
#include <string>

using namespace std;

class A {
public:
    A(){}
    virtual void foo()=0;
    virtual~A(){}
};

class B : public A {
public:
    B(int a = 0) : a(a) {}
    virtual void foo();
    virtual ~B(){}
private:
    int a;
};

void B::foo() {
    cout << "Hello, " << this->a << endl;
}

typedef void (*func)(A*);

int main() {
    A* b = new B();

    void*** vtable = (void***)(b);
    void** func1 = (void**)(*table + 0);
    func p = (func)(*func1);
    p(b); // Hello, 0

    return 0;
}

现在是不是对虚函数、甚至 C++ 对象的实现有了一些认识?祝大家学习愉快。

转载于:https://my.oschina.net/tridays/blog/869159

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值