包含虚函数的类中隐含一个指针,叫做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两次析构函数,没有造成内存泄漏。
原因是因为将子类对象赋给父类时,如果子类对象动态申请了内存空间,而父类析构函数不是虚函数时,子类的析构函数不能调用,所以无法释放内存空间。
参考资料: