C++虚函数与多态

虚函数和多态

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

class A {
};

int main() {
    A a;
    cout << sizeof(a) << endl;		//1
}

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

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

int main() {
    A a;
    cout << sizeof(a) << endl;		//1
}

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

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

int main() {
    A a;
    cout << sizeof(a) << endl;		//8
}

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

class A{
public:
	void* vptr;
	...
};

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

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

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

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

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

class A {
public:
	A() {
		vptr = &A::vtbl;	//编译器做的
		...
	}
	void* vptr;
};

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

class A {
public:
    void func1() {}
    void func2() {}
    virtual void vfunc() {}
    virtual void vfunc2() {}
    virtual ~A() {}
private:
	int m_a;
	int m_b;
};

在这里插入图片描述

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

虚析构的作用

注意上面的例子中把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;
}

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

$ g++ virtual-dtor.cpp 
$ ./a.out 
Derived dtor
Base dtor

如果去掉父类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;
}
$ g++ virtual-dtor.cpp 
$ ./a.out 
Base dtor

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

在这里插入图片描述

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. 当通过父类的指针或引用,调用子类中重写的虚函数时,就能看出多态性的表现了

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

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();
}

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

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

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;
}

设父类有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;
}

在这里插入图片描述
其用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;
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值