虚基类的作用
1 概念
首先还是先给出虚继承和虚基类的定义。
虚继承:在继承定义中包含了virtual关键字的继承关系;虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:
CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以作为不是虚继承体系中的基类。
Virtual 在C++中就是采用了这个词意,不可以在语言模型中直接调用或体现的,但是确实是存在可以被间接的方式进行调用或体现的。比如:虚函数必须要通过一种间接的运行时(而不是编译时)机制才能够**(调用)的函数,而虚继承也是必须在运行时才能够进行定位访问的一种*。存在,但间接。其中关键就在于存在、间接和共享这三种特征。
对于虚函数而言,这三个特征是很好理解的,间接性表明了他必须在运行时根据实际的对象来完成函数寻址,共享性表象在基类会共享被子类重载后的虚函数,其实指向相同的函数入口。
对于虚继承而言,这三个特征如何理解呢?存在即表示虚继承体系和虚基类确实存在,间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成(下面模型中会讲到),共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。
那现在可以解释语法小节中留下来的那个问题了,“为什么一旦出现了虚基类,就必须在每一个继承类中都必须包含虚基类的初始化语句”。由上面的分析可以知道,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中(也是很自然的)选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句,但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句,其他的初始化语句都会被压制不调用。2 模型
为了实现上面所说的三种语义含义,在考虑对象的实现模型(也就是内存模型)时就很自然了。在C++中对象实际上就是一个连续的地址空间的语义代表,我们来分析虚继承下的内存模型。
(1) 存在
也就是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、尾部)则由各个编译器选择,没有差别。(在VC8中无论虚基类被声明在什么位置,虚基类的子对象都会被放置在对象内存的尾部)。(2) 间接
间接性表明了在直接虚继承子类中一定包含了某种指针(偏移或表格)来完成通过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放则虚基类子对象的偏移量的负值,实际上就是个以补码表示的int类型的值,在计算虚基类子对象首地址时,需要将该偏移量取绝对值相加,这个主要是为了和虚表中只能存放虚函数地址这一要求相区别,因为地址是原码表示的无符号int类型的值)。(3) 共享
共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码,体现这些内容。3 性能
由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。(1) 时间
在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。(在VC8中通过打开汇编输出,可以查看*.cod文件中的内容,在访问虚基类对象成员时会形成三条mov间接寻址语句,而在访问一般继承类对象时仅仅只有一条mov常量直接寻址语句)(2) 空间
由于共享所以不存在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。4 实例1,无虚函数的虚基类
当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份。例如:
class CBase { };
class CDerive1:virtual public CBase{ };
class CDerive2:virtual public CBase{ };
class CDerive12:public CDerive1,CDerive2{ };
则在类CDerive12的对象中,仅有类CBase的一个对象数据
虚基类的特点:
虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承);
虚基类的构造函数先于非虚基类的构造函数执行。
#include <iostream>
using namespace std;
//基类
class CBase
{
protected:
int a;
public:
CBase(int na)
{
a = na;
cout << "CBase constructor! \n";
}
~CBase(){cout << "CBase deconstructor! \n"; }
};
//派生类1(声明CBase为虚基类)
class CDerive1 :virtual public CBase
{
public:
CDerive1(int na) :CBase(na)
{
cout << "CDerive1 constructor! \n";
}
~CDerive1(){cout << "CDerive1 deconstructor! \n"; }
int GetA(){return a; }
};
//派生类2(声明CBase为虚基类)
class CDerive2 :virtual public CBase
{
public:
CDerive2(int na) :CBase(na)
{
cout << "CDerive2 constructor! \n";
}
~CDerive2(){cout << "CDerive2 deconstructor! \n"; }
int GetA(){return a; }
};
//子派生类
class CDerive12 :public CDerive1, public CDerive2
{
public:
CDerive12(int na1, int na2, int na3) :CDerive1(na1), CDerive2(na2), CBase(na3)
{
cout << "CDerive12 constructor! \n";
}
~CDerive12(){cout << "CDerive12 deconstructor! \n"; }
};
int main()
{
{
CDerive12 obj(100, 200, 300);
//得到从CDerive1继承的值
cout << "from CDerive1 : a = " << obj.CDerive1::GetA() << endl;
//得到从CDerive2继承的值
cout << "from CDerive2 : a = " << obj.CDerive2::GetA() << endl << endl;
}
system("pause");
return 0;
}
(1) 子派生类对象的值:
从上例可以看出,在类CDerived12的构造函数初始化表中,调用了间接基类CBase的构造函数,这对于非虚基类是非法的,但对于虚基类则是合法且必要的。
对于派生类CDerived1和CDerived2,不论是其内部实现,还是实例化的对象,基类CBase是否是它们的虚基类是没有影响的。受到影响的是它们的派生类CDerived12,因为它从两条路径都能到达CBase。
(2) 内存布局
备注:
vbtable : 虚基类指针表
vftable : 虚函数指针表
由此可知,其公共基类的构造函数只调用了一次,并且优先于非基类的构造函数调用;并且发现,子派生类的对象obj的成员变量的值只有一个,所以,当公共基类CBase被声明为虚基类后,虽然它成为CDerive1和CDerive2的公共基类,但子派生类CDerive12中也只有它的一个备份。可以仔细比较与例2的运行结果有什么不同。
5 实例2,有虚函数的虚基类
下述实例析构函数为虚函数
#include <iostream>
using namespace std;
//基类
class CBase
{
protected:
int a;
public:
CBase(int na)
{
a = na;
cout << "CBase constructor! \n";
}
virtual ~CBase(){cout << "CBase deconstructor! \n"; }
};
//派生类1(声明CBase为虚基类)
class CDerive1 :virtual public CBase
{
public:
CDerive1(int na) :CBase(na)
{
cout << "CDerive1 constructor! \n";
}
~CDerive1(){cout << "CDerive1 deconstructor! \n"; }
int GetA(){return a; }
};
//派生类2(声明CBase为虚基类)
class CDerive2 :virtual public CBase
{
public:
CDerive2(int na) :CBase(na)
{
cout << "CDerive2 constructor! \n";
}
~CDerive2(){cout << "CDerive2 deconstructor! \n"; }
int GetA(){return a; }
};
//子派生类
class CDerive12 :public CDerive1, public CDerive2
{
public:
CDerive12(int na1, int na2, int na3) :CDerive1(na1), CDerive2(na2), CBase(na3)
{
cout << "CDerive12 constructor! \n";
}
~CDerive12(){cout << "CDerive12 deconstructor! \n"; }
};
int main()
{
{
CDerive12 obj(100, 200, 300);
//得到从CDerive1继承的值
cout << "from CDerive1 : a = " << obj.CDerive1::GetA() << endl;
//得到从CDerive2继承的值
cout << "from CDerive2 : a = " << obj.CDerive2::GetA() << endl << endl;
}
system("pause");
return 0;
}
(1) 内存布局
6 实例3,非虚继承
#include <iostream>
using namespace std;
//基类
class CBase
{
protected:
int a;
char b;
public:
CBase(int na)
{
a = na;
cout << "CBase constructor! \n";
}
~CBase() { cout << "CBase deconstructor! \n"; }
};
//派生类1
class CDerive1 : public CBase
{
public:
CDerive1(int na) :CBase(na)
{
cout << "CDerive1 constructor! \n";
}
~CDerive1() { cout << "CDerive1 deconstructor! \n"; }
int GetA() { return a; }
private:
char c;
};
//派生类2
class CDerive2 : public CDerive1
{
public:
CDerive2(int na) :CDerive1(na)
{
cout << "CDerive2 constructor! \n";
}
~CDerive2() { cout << "CDerive2 deconstructor! \n"; }
int GetA() { return a; }
private:
char d;
};
int main()
{
system("pause");
return 0;
}
(1) 内存布局
CDerive1 直接从CBase搬移过来,再加上自己的
CDerive2 直接从CDerive1搬移过来,再加上自己的
本文转自:
http://blog.csdn.net/caomiao2006/article/details/4463664