虚函数原理及应用

来源:互联网 发布:网络维护考题 编辑:程序博客网 时间:2024/04/28 18:55

包含虚函数的类中隐含一个指针,叫做vptr(virtual table pointer,虚函数表指针)。 vptr 指向一个vtbl(virtual table,虚函数表)函数指针数组,该数组记录实际调用的函数地址。对于多继承的时候,情况稍微复杂一些。如果多个父类都包含虚函数,则类中vptr指针也会有多个。当子类对象实例赋值给父类时会对父类中的vptr进行初始化,使其指向含有实际访问的vtbl函数指针数组。

#include <iostream>using namespace std;class empty{};class base{public:    virtual void f(){cout<<"base, virtual function f()"<<endl;}};class derived: public base{public:    virtual void f1(){cout<<"derived, virtual function f1()"<<endl;}};int main(void){    base b;    base *pb;        derived *pd;    void (* pf)(void);    pd = new derived();    cout<<"size of empty class: "<<sizeof(empty)<<endl;    cout<<"size of base class: "<<sizeof(base)<<endl;    cout<<"size of derived class: "<<sizeof(derived)<<endl;    cout<<"base vptr address: "<<(int *)(&b)<<endl;    cout<<"function address in base: "<<(int *)*(int *)(&b)<<endl;    pf = (void (*)()) * ((int *)*(int *)(&b));    (*pf)();    pf();    return 0;}yongmi@yongmi-hn:~/c$ g++ virtual_class.cpp yongmi@yongmi-hn:~/c$ ./a.out size of empty class: 1size of base class: 4size of derived class: 4base vptr address: 0xbfc30f50function address in base: 0x8048c18base, virtual function f()base, virtual function f()

空类的大小为1(每个类实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址),包含虚函数的类大小为4(隐含一个vptr指针)。

关系如下所示:

vptr---->base:f

(int *)(&b)是vptr的地址,存放的内容为*(int *)(&b),即vtbl数组地址。
(int *)*(int *)(&b),将vptr地址中的内容转换为int型指针,即vtbl数组中保存的值转换为int型指针。
(void (*)()) * ((int *)*(int *)(&b)),vtbl数组中保存的指针值转换为函数指针。


上面这个实例中可以看出,通过获取vptr可以通过函数指针来访问类中的成员函数(即使该成员函数为私有也能访问),这一点有违类的访问控制权限。


C++父类的析构函数应该使用虚函数,否则可能会造成内存泄漏。看下面这个例子:

#include <iostream>using namespace std;class base{public:    ~base ()    {        cout << "base, ~base()" << endl;    }};class derived:public base{private:    char *name;public:    derived ()    {        name = new char[10];    }     virtual ~derived ()    {        delete []name;        cout << "derived, ~derived()" << endl;    }};int main (void){    derived d;        base *pb = new derived ();    delete pb;} yongmi@yongmi-hn:~/c$ g++ virtual_constructor.cppyongmi@yongmi-hn:~/c$ ./a.out base, ~base()derived, ~derived()base, ~base()

运行结果显示,类derived的析构函数只调用了一次,造成子类中申请的内存泄漏。将程序修改之后如下:

#include <iostream>using namespace std;class base{public:    virtual ~base ()    {        cout << "base, ~base()" << endl;    }};class derived:public base{private:    char *name;public:    derived ()    {        name = new char[10];    } virtual ~derived ()    {        delete []name;        cout << "derived, ~derived()" << endl;    }};int main (void){    derived d;        base *pb = new derived ();    delete pb;} yongmi@yongmi-hn:~/c$ g++ virtual_constructor.cppyongmi@yongmi-hn:~/c$ ./a.out derived, ~derived()base, ~base()derived, ~derived()base, ~base()

此时程序调用了类derived两次析构函数,没有造成内存泄漏。


原因是因为将子类对象赋给父类时,如果子类对象动态申请了内存空间,而父类析构函数不是虚函数时,子类的析构函数不能调用,所以无法释放内存空间。




参考资料:

C++ 虚函数表解析

C++中虚函数工作原理和()继承类的内存占用大小计算

C++虚函数的原理

C++中基类的析构函数为什么要用virtual虚析构函数