《More effective C++》 Item24
基础概念
虚函数的实现
两个关键词:virtual table和virtual table pointers,通常称为vtbl和vptr。
vtbl通常是一个函数指针数组。
Base *p = new Derive();
p->Print();
(*p->vptr[i])(p);
虚函数不能是内联函数。
因为内联函数在编译期间用被调用的函数体本身来代替函数调用的指令。
多重继承对虚函数带来的问题
虚函数表是属于类的,所以在多重继承里,在单个对象里就会有多个vptr(每个基类对应一个),除了自己的vtbl之外,还需要为基类生成特殊的vtbl,因此增加了每个类和每个对象中的虚函数额外占用的空间,而且运行时调用所需的代价也增 加了一些。
多重继承带来的虚继承问题
多继承经常导致对虚基类的需求。没有虚基类,如果一个派生类有一个以上从基类的继承路径,基类的数据成员被复制到每一个继承类对象里,继承类与基类间的每条路径都有一个拷贝。
菱形继承的缺点:
数据冗余:在Derive中会保存两份Base基类的内容
访问不明确(二义性):因为Derive不知道是以B为中介去访问A还是以C为中介去访问A,因此在访问某些成员的时候会发生二义性。
class A
{
public:
A(int a) :m_a(a) {}
int getMa() { return m_a; }
private:
int m_a;
};
class B :public A
{
public:
B(int a, int b) :A(a), m_b(b) {}
private:
int m_b;
};
class C :public A
{
public:
C(int a, int c) :A(a), m_c(c) {}
private:
int m_c;
};
class D :public B, public C
{
public:
D(int a, int b, int c, int d) :B(a, b), C(a, c), m_d(d) {}
void func()
{
/*错误,访问不明确
std::cout << getMa();*/
//正确,通过B访问getMa()
std::cout << B::getMa();
}
private:
int m_d;
};
虚继承
虚继承的作用:为了保证公共继承对象在创建时只保存一分实例
虚继承解决了菱形继承的两个问题:
数据冗余:顶级基类在整个体系中只保存了一份实例
访问不明确(二义性):可以不通过作用域访问符::来调用(原理就是因为顶级基类在整个体系中只保存了一份实例)
class CBase { };
class CDerive1:virtual public CBase{ };
class CDerive2:virtual public CBase{ };
class CDerive12:public CDerive1,CDerive2{ };
虚继承的实现原理
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。
在这里我们可以对比虚函数的实现原理:他们有相似之处,都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)。
虚基类依旧存在继承类中,只占用存储空间;虚函数不占用存储空间。
虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。
#include<iostream>
using namespace std;
class A //大小为4
{
public:
int a;
};
class B :virtual public A //大小为12,变量a,b共8字节,虚基类表指针4
{
public:
int b;
};
class C :virtual public A //与B一样12
{
public:
int c;
};
class D :public B, public C //24,变量a,b,c,d共16,B的虚基类指针4,C的虚基类指针
{
public:
int d;
};
int main()
{
A a;
B b;
C c;
D d;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
cout << sizeof(d) << endl;
system("pause");
return 0;
}
reference
[1] https://blog.51cto.com/u_15346415/3674072
[2] https://www.yangshuaibin.com/detail/376699
[3] https://blog.csdn.net/bxw1992/article/details/77726390
网友评论