#include<iostream>
using namespace std;
class A {
public:
char ch[1];
virtual void fun() {
}
};
class B : virtual public A {
public:
char ch[1];
//不管B中的fun是不是virtual,B的大小都是16,因为虚函数表的指针只有一份,只占用4
//A中有虚函数表,所以不管B的fun是不是virtual,4已经被占用了
//但是如果是virtual void fun2(),就会额外占用4个字节
virtual void fun() {
}
};
class C : virtual public B {
public:
char ch[1];
virtual void fun() {
}
};
int main(void)
{
A a;
B b;
C c;
int sizea = sizeof(a); //8
int sizeb = sizeof(b); //16
int sizec = sizeof(c); //24
return 0;
}
因为虚继承增加了一个指向父类的虚函数指针。。。。所以空间增加了4
特别注意的是,由于class B:virtual public A
会把A中的变量和虚表都单独存一份,所以+8,如果A中没有虚表,+4,例如下面这个例子。。。。
对于下面的继承,大小是32,
#include<iostream>
using namespace std;
class CommonBase
{
int co;
};
class Base1 : virtual public CommonBase
{
public:
int b1;
virtual void print1() { }
};
class Base2 : virtual public CommonBase
{
public:
int b2;
virtual void dump1() { }
};
class Derived : public Base1, public Base2
{
public:
int d;
void print2() { }
void dump2() { }
};
int main(void)
{
Derived d;
int sized = sizeof(d);
return 0;
}
内存中的存放方式为:
4+4+4+4: co, b1, b2, d;
4+4: base1和base2的虚函数表,由于public 了两个,所以占两个空间
4+4: base1和base2的虚记标,由于是虚继承了两个,所以也两个空间
class Derived size(32):
+---
| +--- (base class Base1)
| | {vfptr}
| | {vbptr}
| | b1
| +---
| +--- (base class Base2)
| | {vfptr}
| | {vbptr}
| | b2
| +---
| d
+---
+--- (virtual base CommonBase)
| co
+---
但是对于下面的继承方式,由于发生了虚函数的覆盖,大小为28
#include<iostream>
using namespace std;
class A {
public:
char ch[1];
virtual void fun() {
}
};
class B : virtual public A {
public:
char ch[1];
virtual void fun() {
}
};
class C : virtual public A {
public:
char ch[1];
virtual void fun() {
}
};
class D : public B, public C {
public:
char ch[1];
virtual void fun() {
}
};
int main(void)
{
A a;
B b;
C c;
D d;
int sizea = sizeof(a); //8
int sizeb = sizeof(b); //16
int sizec = sizeof(c); //16
//28的组成
//A:ch由于虚继承只有1份,占4
//B::ch,C::ch,D::ch各占4
//B->A和C->A的虚基表各占4
//fun的虚函数表的指针,占4
int sized = sizeof(d); //28
return 0;
}
但如果多一个virtual,大小也是28,多两个virtual,大小是32,说明指向虚基类的指针也是放在第1个多继承末尾的。。。
#include<iostream>
using namespace std;
class A {
public:
char ch[1];
virtual void fun() {
}
};
class B: virtual public A {
public:
char ch[1];
virtual void fun() {
}
};
class C: virtual public A {
public:
char ch[1];
virtual void fun() {
}
};
class D: public B,virtual public C {
public:
char ch[1];
virtual void fun() {
}
};
int main(void)
{
A a;
B b;
C c;
D d;
int sizea = sizeof(a); //8
int sizeb = sizeof(b); //16
int sizec = sizeof(c); //16
int sized = sizeof(d); //28
return 0;
}
32的情况:
#include<iostream>
using namespace std;
class A {
public:
char ch[1];
virtual void fun() {
}
};
class B: virtual public A {
public:
char ch[1];
virtual void fun() {
}
};
class C: virtual public A {
public:
char ch[1];
virtual void fun() {
}
};
class D: virtual public B,virtual public C {
public:
char ch[1];
virtual void fun() {
}
};
int main(void)
{
A a;
B b;
C c;
D d;
int sizea = sizeof(a); //8
int sizeb = sizeof(b); //16
int sizec = sizeof(c); //16
int sized = sizeof(d); //32
return 0;
}
以下转自:https://www.jianshu.com/p/65477aa6787d
当某类的部分或者全部直接基类是从另一个共同基类派生而来时,在这些直接基类中,从上级共同基类继承来的成员就拥有相同的名称。在派生类的对象中,这些同名数据成员在内存中同时拥有多个拷贝,同一函数名会有多个映射。我们可以用作用域分辨符来唯一标识并分别访问它们。
也可以将共同的基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
语法:
class 派生类名:virtual 继承方式 基类名
上述语句声明基类为派生类的虚基类。在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。声明了虚基类以后,虚基类的成员在进一步派生的过程中和派生类一起维护同一个内存数据拷贝。
------------------------------------------------------------------------------------------------
以下转自: http://blog.csdn.net/lovemysea/article/details/5298853
C++虚基类构造函数
下面文章详细介绍C++虚基,所谓C++虚基类:是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的,但前提是要深入理解到底什么是C++虚基类,及他是怎么运行的。
前面讲过,为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。
由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最派生类。C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的C++虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。
从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数的调用。但是,只有用于建立对象的那个最派生类的构造函数调用虚基类的构造函数。
而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略(例如下面例子中的BC),这样便保证了对虚基类的对象只初始化一次。C++又规定,在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则C++虚基类的构造函数先于非虚基类的构造函数的执行。
下面举一例子说明具有C++虚基类的派生类的构造函数的用法。
class A
{
public:
A(const char *s)
{
cout<<s<<endl;// ~A() {}
}
};
class B : virtual public A
{
public:
B(const char *s1, const char *s2):A(s1) { cout<<s2<<endl; }
};
class C : virtual public A
{
public: C(const char *s1, const char *s2):A(s1) { cout<<s2<<endl; }
};
class D : public B, public C
{
public: D(const char *s1, const char *s2, const char *s3, const char *s4) :B(s1, s2), C(s1, s3), A(s1)
{
cout<<s4<<endl;
}
};
void main()
{
D *ptr = new D("class A", "class B", "class C", "class D");
delete ptr;
ptr = NULL;
}
打印结果:
class A
class B
class C
class D
在派生类B和C中使用了C++虚基类,使得建立的D类对象只有一个虚基类子对象。在派生类B,C,D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数。在建立类D对象时。
只有C++虚基类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,并且仅调用一次,而类D基类的构造函数的成员初始化列表中列出的虚基类构造函数不被执行。这一点将从该程序的输出结果可以看出。