C++虚函数表

一、虚函数和纯虚函数

1.1 虚函数

在类成员方法的声明 (不是定义) 语句前加 “virtual”,如 virtual void func()

class ISpeaker {
public:
    virtual void func();
};

1.2 纯虚函数

在虚函数后加 “=0”,如 virtual void func()=0

class ISpeaker {
public:
    virtual void func() = 0;
};

1.3 override和final说明符

1、分清重写、重载和隐藏

重载 (overload) ,它通常是指在同一个类中有两个或者两个以上函数,它们的函数名相同,但是函数形参不同

重写 (override) 的意思更接近覆盖,在C++中是指派生类覆盖了基类的虚函数

隐藏 (overwrite) 是指基类成员函数,无论它是否为虚函数,当派生类出现同名函数时,如果派生类函数形参基类函数,则基类函数会被隐藏。如果派生类函数形参与基类函数相同,则需要确定基类函数是否为虚函数,如果是虚函数这里的概念就是重写,否则基类函数也会被隐藏。

2、为什么要用override

重写会引发下列问题。下面的代码可以编译成功,但是派生类的四个函数都没有触发重写操作,如果测试人员没有经过仔细的测试,在软件发布前很难意识到这些问题

class Base {
public:
    virtual void some_func() { }
    virtual void foo(int x) { }
    virtual void bar() const { }
    void baz() { }
};

class Derived : public Base {
public:
    virtual void sone_func() { }  // (1)
    virtual void foo(int &x) { }  // (2)
    virtual void bar() { }        // (3)
    virtual void baz() { }        // (4)
};

(1) 派生类虚函数的函数名与基函数的虚函数不同,派生类的函数不是重写
(2) 派生类虚函数的形参列表与基类虚函数的不同,派生类的函数不是重写
(3) 派生类虚函数相对于基类的虚函数少了常量属性,派生类的函数不是重写
(4) 基类成员函数根本不是虚函数,派生类的函数不是重写

让编译器去检查继承中是否发生了重写是非常简单的一件事,只需要告诉编译器,我要重写哪些函数就行了,C++11 标准引入了 override 这个说明符,它就是用来做这件事的

class Derived : public Base {
public:
    virtual void sone_func() override { }
    virtual void foo(int &x) override { }
    virtual void bar() override { }
    virtual void baz() override { }
};

这样一修改,四个函数都引发了编译失败,比人工检查要方便很多

3、final说明符

C++11 引入 final 关键字,用来阻止派生类去继承基类的虚函数。它告诉编译器,该虚函数不允许被重写

class Base {
public:
    virtual void foo(int x) { }
};

class Derived : public Base {
public:
    void foo(int x) final { }  // (1)
};

class Derived2 : public Derived {
public:
    void foo(int x) { }
};

(1) 中间层的基函数 derived 的虚函数 foo 声明位 final,所以派生类 Derived2 重写 foo 函数时,编译器会给出错误提示

处理用于声明虚函数,final 还可以声明 类,在类名的后面声明 final,可以阻止该类被继承。如下代码所示,最终会编译失败

class Base final {
public:
    virtual void foo(int x) { }
};

class Derived : public Base {
public:
    void foo(int x) { }
};

1.4 其他区别

虚函数 纯虚函数
子类可以 (也可以不) 重新定义基类的虚函数,该行为称之为复写Override 子类必须提供纯虚函数的个性化实现
子类如果不提供虚函数的实现,将会自动调用基类的缺省虚函数实现,作为备选方案 子类如果不提供纯虚函数的实现,编译将会失败。尽管在基类中可以给出纯虚函数的实现,但无法通过指向子类对象的基类类型指针来调用该纯虚函数,也即不能作为子类相应纯虚函数的备选方案

二、多态

2.1 多态

两个不同的子类强转为基类,调用这个基类的相同方法,两个不同的子类执行各自的函数

#include 

class ISpeaker
{
protected:
    size_t b;
public:
    ISpeaker(size_t _v) : b(_v) { }
    virtual void speak() = 0;
};

class Dog : public ISpeaker {
public:
    Dog() : ISpeaker(0) { }
    virtual void speak() override { 
        printf("woof! %llu\n", b);
    }
};

class Human : public ISpeaker {
private:
    size_t c;
public:
    Human() : ISpeaker(1), c(2) { }
    virtual void speak() override { 
        printf("hello! %llu\n", c);
    }
};

int main(int argc, char** _argv) {
    Human* pHuman = new Human();
    Dog* pDog = new Dog();

    ISpeaker* speaker1 = (ISpeaker*)pHuman;
    ISpeaker* speaker2 = (ISpeaker*)pDog;

    speaker1->speak();
    speaker2->speak();
}

运行结果:
C:\Users\huan\Desktop>.\a.exe 
hello! 2
woof! 0

2.2 多态的实现——虚函数表

如果一个类实现了一个虚函数,编译器在处理它的时候会生成一个虚函数表

C++虚函数表_第1张图片

虚函数表的定义如下:

struct SpeakerTable {
    void(*speak)(void* _ptr);
};

子类的内存模型如下,狗和人类的虚函数表指针指向的结构体不同

C++虚函数表_第2张图片

2.3 代码实现编译器内容

相当于原来的子类结构体强转为父类的结构体,子类和父类第一位都是虚函数表指针,然后调用父类虚函数表中的函数,对应着调用子类虚函数表中的函数

#include 

struct SpeakerTable {
    void(*speak)(void* _ptr);
};

void __dog_speak(void* _ptr) {
    uint8_t* p = (uint8_t*)_ptr;
    p += sizeof(SpeakerTable*);
    size_t b = *((size_t*)p);
    printf("woof! %11u\n", b);
}

void __human_speak(void* _ptr) {
    uint8_t* p = (uint8_t*)_ptr;
    p += sizeof(SpeakerTable*);
    p += sizeof(size_t);
    size_t b = *((size_t*)p);
    printf("hello! %11u\n", b);
}

const static SpeakerTable __dogSpeakTable = {
    __dog_speak
};

const static SpeakerTable __humanSpeakTable = {
    __human_speak
};

struct __ispeaker {
    const SpeakerTable* vt;
    size_t b;
};

struct __dog {
    const SpeakerTable* vt;
    size_t b;
};

struct __human {
    const SpeakerTable* vt;
    size_t b;
    size_t c;
};

__dog* createDog() {
    __dog* ptr = (__dog* )malloc(sizeof(__dog));
    ptr->vt = &__dogSpeakTable;
    ptr->b = 0;
    return ptr;
}

__human* createHuman() {
    __human* ptr = (__human*)malloc(sizeof(__human));
    ptr->vt = &__humanSpeakTable;
    ptr->b = 1;
    ptr->c = 2;
    return ptr;
}

int main(int _argc, char** _argv)
{
    __dog* dog = createDog();
    __human* human = createHuman();

    __ispeaker* speaker1 = (__ispeaker*)dog;
    __ispeaker* speaker2 = (__ispeaker*)human;

    speaker1->vt->speak(speaker1);
    speaker2->vt->speak(speaker2);

    return 0;
}

你可能感兴趣的:(C/C++,c++)