虚函数原理及应用

包含虚函数的类中隐含一个指针,叫做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: 1
size of base class: 4
size of derived class: 4
base vptr address: 0xbfc30f50
function address in base: 0x8048c18
base, 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.cpp
yongmi@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.cpp
yongmi@yongmi-hn:~/c$ ./a.out 
derived, ~derived()
base, ~base()
derived, ~derived()
base, ~base()

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


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




参考资料:

C++ 虚函数表解析

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

C++虚函数的原理

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值