继承模板类(静态成员)
- 两个子类使用同一个类型继承,base和test由于继承object类中的静态成员变量,且都是int类型继承,因此只调用推演一次,num也只初始化一次。
template<class T>
class Object
{
private:
T value;
public:
Object(T x = T()) { value += 1; }
static int num;
};
template<class T>
int Object<T>::num = 0;
class Base:public Object<int>
{
public:
Base() { num += 1; }
void Print() { cout << "Base:" << num << endl; }
};
class Test :public Object<int>
{
public:
Test() { num += 1; }
void Print() { cout << "Test:" << num << endl; }
};
int main()
{
Base t1, b1;
Test t2, b2;
t1.Print(); //此时静态成员变量,存放在数据区,模板类的类型为
//在类外进行初始化,且进初始化一次,因此t1,b1,t2,b2操作的都是同一个num
t2.Print();
}
如下图:
结果:
2.base用double类型继承,test用int类型继承,会推演出两个模板,自然而然,在各自的类型模板中,对静态成员变量只初始化一次
class Base:public Object<double>
{};
class Test :public Object<int>
{};
结果:
菱形继承和虚继承
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x)
{}
};
class Base:public Object
{
int num;
public:
Base(int x = 0) :num(x), Object(x + 10) {}
};
class Test :public Object
{
int sum;
public:
Test(int x = 0):sum(x),Object(x+10){}
};
class Det :public Base, public Test
{
int total;
public:
Det(int x = 0) :total(x), Base(x + 10), Test(x + 20), Object(x + 100) {}
};
int main()
{
Det d(0);
Base b1 = d;//ok
Test t1 = d;//ok
Object op = d;//error op指向d,d继承了Base和Test,所以op不知道指向的是继承base的d还是继承了Test的d,
}
出现的问题:
从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Det的对象中Object成员会有两份。
解决方案:采用虚继承:
虚继承:主要是通过虚继承,在每个派生类中会有一个虚基类指针(占四个字节),和虚基类表(不占空间);当虚继承的派生类被当做基类再次虚继承,虚基类指针也会被继承。
(因此每个派生类既可以通过虚基类指针,查找虚基类表找到中的成员)
注意:
1. 一个类可以在一个类族中用作虚基类,也可以用作非虚基类。
2. 在派生类的对象中,同名的虚基类**只产生一个虚基类子对象**,而某个非虚基类产生各自的对象。
3. 虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化 (最派生类会先去调用虚基类的构造函数)。
4. 在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数。
5. 在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。
6. 虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。
//虚基类
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x)
{}
};
class Base:virtual public Object
{ //存在虚基类指针和虚基类表
int num;
public:
Base(int x = 0) :num(x), Object(x + 10) {}
};
class Test :virtual public Object
{ //存在虚基类指针和虚基类表
int sum;
public:
Test(int x = 0):sum(x),Object(x+10){}
};
class Det :public Base, public Test
{
int total;
public:
//虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化 (最派生类会先去调用虚基类的构造函数)。 优先初始化Object
Det(int x = 0) :total(x), Base(x + 10), Test(x + 20), Object(x + 100) {}
};
int main()
{
Det d(0);
}
步骤:
- 调用Object的构造函数,初始化虚基类,创建隐藏基类对象,保存虚表指针和,虚表
- 在初始化Base,这个时候,Base由于虚继承Object,因此Base类中会有一个虚基类指针,和虚基类表。不需要在调动构造函数创建Object的创建隐藏基类对象。
- 初始化Test和Base相同。
- 对Det进行初始化,创建Det对象d
如下图:(虚基表指针存放在数据区,存放在对象的开头)
sizeof(d)
对于Object基类,Base和Test如何找到它的?
其实两个派生类里面并不是指针,而是记录了一个偏移量,因为在构造过程中,这一块内存就在一起,不需要拿指针指向某个位置,又不是分散在内存其他地方,书上所说的vbptr是个指针,是为了更好理解。
根据d的地址:在base中,有一个偏移量 0x14,就是十进制的20,意味着如果要找到Object基类,需要偏移20字节。
同理:Test里面存放了一个偏移量 0x 0c,即十进制的12,要找到基类Base,需要偏移12个字节。
在菱形继承中加入虚函数。
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x)
{}
virtual void fun() {}
virtual void add() {}
};
class Base :public Object
{
int num;
public:
Base(int x = 0) :num(x), Object(x + 10) {}
virtual void fun() {}
};
class Test :public Object
{
int sum;
public:
Test(int x = 0) :sum(x), Object(x + 10) {}
virtual void fun() {}
void add() {}
};
class Det :public Base, public Test
{
int total;
public:
Det(int x = 0) :total(x), Base(x + 10), Test(x + 20){}
void fun() {}
void add() {}
};
Object基类中的虚表指针,开始指向Object中的虚表,
当程序运行到,构建Det对象,因为Det继承了Base和Test类,
用两种方式去分别继承Base和Test类,因此Object中的虚表指针,在指向Det中的虚表时,有两个,一个继承Base中的虚表,另一个是继承test中的虚表