当前位置:   article > 正文

C++虚函数与多态_c++ 结构体 虚函数

c++ 结构体 虚函数

虚函数和多态

首先明确一个空类产生的对象的大小为1B,即使是一个空类,其实例化的对象至少占用1B的内存空间

class A {
};

int main() {
    A a;
    cout << sizeof(a) << endl;		//1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后我们向类A中加入两个普通成员函数,对象的大小还是1B:

class A {
public:
    void func1() {}
    void func2() {}
};

int main() {
    A a;
    cout << sizeof(a) << endl;		//1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这说明A的普通成员函数,并不会占用类对象的内存空间。然而一旦我们向A中加入一个虚函数,其对象的大小变为8B:

class A {
public:
    void func1() {}
    void func2() {}
    virtual void vfunc() {}
};

int main() {
    A a;
    cout << sizeof(a) << endl;		//8
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

原因:当引入虚函数后,编译器在类中插入了一个虚函数表指针vptr,类似于下面的伪码:

class A{
public:
	void* vptr;
	...
};
  • 1
  • 2
  • 3
  • 4
  • 5

而vtpr是占用类对象的内存空间的:

(gdb) p a
$1 = (A) {_vptr.A = 0x7ff771b64520 <vtable for A+16>}
  • 1
  • 2

当类A中至少包含一个虚函数,编译器会为类A产生一个虚函数表vtbl,这个虚函数表会一直伴随着类A,包括其装入内存

虚函数表指针被赋值的时机:执行A的构造函数时,让对象的虚函数指针指向类A的vtbl

每个类对象的虚函数表指针vptr指向这个类的虚函数表vtbl,编译器在编译期间在类A的构造函数内安插vptr的赋值语句,类似于下面的伪码:

class A {
public:
	A() {
		vptr = &A::vtbl;	//编译器做的
		...
	}
	void* vptr;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

考虑如下的A类对象的内存布局:

class A {
public:
    void func1() {}
    void func2() {}
    virtual void vfunc() {}
    virtual void vfunc2() {}
    virtual ~A() {}
private:
	int m_a;
	int m_b;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

(gdb) p a
$1 = (A) {_vptr.A = 0x7ff77a105520 <vtable for A+16>, m_a = 0, m_b = 1}
  • 1
  • 2

虚析构的作用

注意上面的例子中把A的析构函数设为虚函数,在实际开发中,这样做的目的是保证当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用,否则其只会调用基类的析构函数,如果派生类中有指针成员持有堆区内存,就得不到释放而造成内存泄漏

例如,这是正确的形式:

#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base() {
        cout << "Base dtor" << endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        cout << "Derived dtor" << endl;
    }

};

int main () {
    Base* pb = new Derived();
    delete pb;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

当delete父类指针时,子类的dtor也一同被调用:

$ g++ virtual-dtor.cpp 
$ ./a.out 
Derived dtor
Base dtor
  • 1
  • 2
  • 3
  • 4

如果去掉父类dtor前的virtual,则只会调用父类的dtor:

#include <iostream>
using namespace std;

class Base {
public:
    ~Base() {
        cout << "Base dtor" << endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        cout << "Derived dtor" << endl;
    }

};

int main () {
    Base* pb = new Derived();
    delete pb;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
$ g++ virtual-dtor.cpp 
$ ./a.out 
Base dtor
  • 1
  • 2
  • 3

多态:当通过父类指针指向子类对象,或通过父类引用绑定子类对象,调用父类中的虚函数,实际调用的是对应子类的虚函数

在这里插入图片描述

class Base {
public:
    virtual void myvirfunc() {}
};

int main() {
    Base* pb = new Base();
    pb->vfunc();	//this is polymorphic

    Base b;
    b.vfunc();		//this is not polymorphic

    Base* pb2 = &b;
    pb2->vfunc();	//this is polymorphic
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

多态的表现:

  1. 程序中既存在父类也存在子类,父类中必须含有虚函数,子类中也必须重写父类中的虚函数
  2. 父类指针指向子类对象,或者父类引用绑定子类对象
  3. 当通过父类的指针或引用,调用子类中重写的虚函数时,就能看出多态性的表现了

下面的调用全是多态调用:

class Base {
public:
    virtual void myvirfunc() {}
};

class Derive : public Base {
public:
    virtual void myvirfunc() {}
};

int main() {
    //父类指针指向子类对象
    Derive d;
    Base* pb = &d;
    pb->myvirfunc();

    Base* pb2 = new Derive();
    pb2->myvirfunc();

    //父类引用绑定子类对象
    Derive d2;
    Base& rb = d2;
    rb.myvirfunc();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

存在继承关系时虚函数表指针指向:

考虑下面的继承关系和重写:

class Base {
public:
	virtual void f() {}
	virtual void g() {}
	virtual void h() {}
};

class Derive :public Base {
public:
	virtual void g() {}		//rewrite g()
};

int main(int argc, char* argv[]) {
	Base b;
	Derive d;
	system("pause");
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

设父类有f(), g(), h()这三个虚函数,子类重写了父类中的g()虚函数,则父子类对象的虚函数表指针和虚函数表如下:

在这里插入图片描述

延申思考题:

  1. 当进行多重继承时,子类对象有几个虚函数表指针?子类对象有几个虚函数表?
  2. 虚基类表指针在对象内存中的布局?

用C语言模拟多态

对于下面的多态实例:

#include <iostream>
using namespace std;

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

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

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

int main(){
    ISpeaker* d = new Dog();
    ISpeaker* h = new Human();

    d->speak();
    h->speak();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

在这里插入图片描述
其用C语言可以描述如下:

#include <iostream>
using namespace std;

extern "C" {
	//虚函数表类型
    struct vft {
        void (*speak) (void* ptr);
    };
	//Dog的speak方法
   void Dog_speak (void* ptr) {
        void* p = ptr + sizeof(vft*);
        int bb = *((int*)p);
        printf("woof %d\n", bb);
    }
	//Human的speak方法
    void Human_speak (void* ptr) {
        void* p = ptr + sizeof(vft) + sizeof(int);
        int cc = *((int*)p);
        printf("hello %d\n", cc);
    }
	//Dog的虚函数表,其speak函数指针指向Dog_seapk
    const static vft Dog_vft= {
        .speak = Dog_speak
    };
	//Human的虚函数表,其speak函数指针指向Human_speak
    const static vft Human_vft = {
        .speak = Human_speak
    };
	//基类型内存模型
    struct ISpeaker {
        const vft* vptr;
        int b;
    };
	//Dog类型内存模型
    struct Dog {
        const vft* vptr;
        int b;
    };
	//Human类型内存模型
    struct Human {
        const vft* vptr;
        int b;
        int c;
    };
 
    Dog* Dog_constructor() {
        Dog* d =(Dog*)malloc(sizeof(Dog));
        d->vptr = &Dog_vft;
        d->b = 0;
        return d;
    }

    Human* Human_constructor() {
        Human* h =(Human*)malloc(sizeof(Human));
        h->vptr = &Human_vft;
        h->b = 1;
        h->c = 2;
        return h;
    }   

    int main () {
        ISpeaker* s1 = (ISpeaker*)Dog_constructor();
        ISpeaker* s2 = (ISpeaker*)Human_constructor();
        
        s1->vptr->speak(s1);
        s2->vptr->speak(s2);
        return 0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/43436?site
推荐阅读
相关标签
  

闽ICP备14008679号