C++虚表问题

请教一个问题,C++中从虚基类派生出来的类,如果原虚基类中的虚函数在该类中还是虚函数,那么系统会不会给这个派生类创建虚表?或者是只分配一个指针指向第一次出现这些虚函数的基类的虚表?
1楼的朋友谢谢你的回答
我喜欢结交你这样的有研究精神的朋友,你的分析很有研究价值,暂且先不讨论编译器的问题。
我从深入浅出MFC中找到虚表的相关知识,这个肯定不会有错,期待问题能够尽早彻底解决
深入浅出MFC:
1.每一个内涵虚函数的类,编译器都为它做出一个虚拟函数表,表中的每一个元素都指向一个虚函数的地址。
此外,编译器当然也会为类表加上一项成员函数,是一个指向该虚拟函数表的指针(常被称为vptr),每一个由此类别派生出来的类,都有这么一个vptr。
2.虚表以及这种间接呼叫方式。虚表的内容是依据类别中的虚函数声明次序--填入函数指针。派生类别会继承基础类别的虚表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚表就受了影响;表中的元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。
总结:通过深入浅出MFC的内容可以得知:
1,派生类要继承基类的虚表,而不只是说继承基类的虚表指针,所以可以判定,至少从理论上可以判定虚表是要拷贝的,而不是继续用父类虚表,而且多想想也应该可以理解,父类对象指针所指的虚表内容应该和子类不同,因为刚才有提到虚表函数的重定向。可能编译器做了某种优化,再研究

对于一个类如果有虚函数,就会在这个类中创建一个虚表,也就会产生一个虚指针指向这个虚表。
既然有一个指针指向了虚表,这个类派生后,在派生类中就不必再创建虚表,如果派生类还有自己的虚函数,那么只在派生类中创建该虚函数的一个虚表,产生一个指向该虚表的指针。
为每个类设置虚表,初始化虚指针,为虚函数调用插入代码都是自动发生的,不必担心这些。

(我看到过虚继承下虚表问题的分析,直接继承下没看过,特此又分析了一下,修改)
#include<iostream>
using namespace std;
class A
{

public:
virtual void a(){};
};
class B:public virtual A
{

public:
virtual void b(){};

};

int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
}

对于这个程序,你大概是在vc6.0下运行的吧,在vc下结果是4和12。我用的编译器是g++,对于这段程序的结果确实都为4.

因编译器不同导致结果不同这个我倒是没注意,这样可以得出,对虚函数这里的处理机制和编译器也是有关的,g++编译器还是更符合新的标准。

把虚继承virtual去掉再运行,在codeblocks下,g++编译运行结果都为4. vc下编译运行结果也都为4.

在分析一个例子:
#include<iostream>
using namespace std;
class A
{
char j[3];
public:
virtual void a(){};
};
class B:public A
{
char k[3];
public:
virtual void b(){};

};
class C:public B
{
char m[3];
public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
这个直接继承的例子,在vc下,codeblocks下结果都为 8 12 16.

再看这个直接继承的例子:
#include<iostream>
using namespace std;
class A
{

public:
virtual void a(){};
};
class B:public A
{

public:
virtual void b(){};

};
class C:public B
{

public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
vc下河codeblocks下结果都为 4. 这两个程序说明,直接继承下,输出结果应当是本类所占的字节加父类数据成员所占字节,父类的虚指针所占字节没有加上。

加入虚继承后:
#include<iostream>
using namespace std;
class A
{
char j[3];
public:
virtual void a(){};
};
class B:public virtual A
{
char k[3];
public:
virtual void b(){};

};
class C:public virtual B
{
char m[3];
public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
这段代码,在codeblocks下g++编译运行结果为:8 16 24.
在vc6.0下编译运行结果为:8 20 32.
能够分析出codeblocks下的结果是本类加上父类的结果。vc下是 本类加父类后又加了4个字节。

但是这段代码:
#include<iostream>
using namespace std;
class A
{

public:
virtual void a(){};
};
class B:public virtual A
{

public:
virtual void b(){};

};
class C:public virtual B
{

public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
codeblocks下编译运行结果为 4 4 4.
vc下编译运行结果为:4 12 20.
从结果来看,vc下是 本类加父类后又加了4个字节。而codeblocks结果就不再是本类加父类的结果(这个有点迷茫)。

我只能提供这么多信息了~
如果你有更好的解释可以给我留言,相互学习~
温馨提示:内容为网友见解,仅供参考
第1个回答  2015-12-15
虚函数表是编译器用来实现多态的方法。一般来说,虚函数表是一个数组,数组的元素是虚函数的指针。每个对象都会有一个虚函数表,当在某个对象上调用虚函数时,通过查询这个表来获得继承层次中到底哪个函数被实际调用。
对于一个类如果有虚函数,就会在这个类中创建一个虚表,也就会产生一个虚指针指向这个虚表。
既然有一个指针指向了虚表,这个类派生后,在派生类中就不必再创建虚表,如果派生类还有自己的虚函数,那么只在派生类中创建该虚函数的一个虚表,产生一个指向该虚表的指针。
为每个类设置虚表,初始化虚指针,为虚函数调用插入代码都是自动发生的,不必担心这些。

(我看到过虚继承下虚表问题的分析,直接继承下没看过,特此又分析了一下,修改)
#include<iostream>
using namespace std;
class A
{

public:
virtual void a(){};
};
class B:public virtual A
{

public:
virtual void b(){};

};

int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
}

对于这个程序,你大概是在vc6.0下运行的吧,在vc下结果是4和12。我用的编译器是g++,对于这段程序的结果确实都为4.

因编译器不同导致结果不同这个我倒是没注意,这样可以得出,对虚函数这里的处理机制和编译器也是有关的,g++编译器还是更符合新的标准。

把虚继承virtual去掉再运行,在codeblocks下,g++编译运行结果都为4. vc下编译运行结果也都为4.

在分析一个例子:
#include<iostream>
using namespace std;
class A
{
char j[3];
public:
virtual void a(){};
};
class B:public A
{
char k[3];
public:
virtual void b(){};

};
class C:public B
{
char m[3];
public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
这个直接继承的例子,在vc下,codeblocks下结果都为 8 12 16.

再看这个直接继承的例子:
#include<iostream>
using namespace std;
class A
{

public:
virtual void a(){};
};
class B:public A
{

public:
virtual void b(){};

};
class C:public B
{

public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
vc下河codeblocks下结果都为 4. 这两个程序说明,直接继承下,输出结果应当是本类所占的字节加父类数据成员所占字节,父类的虚指针所占字节没有加上。

加入虚继承后:
#include<iostream>
using namespace std;
class A
{
char j[3];
public:
virtual void a(){};
};
class B:public virtual A
{
char k[3];
public:
virtual void b(){};

};
class C:public virtual B
{
char m[3];
public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
这段代码,在codeblocks下g++编译运行结果为:8 16 24.
在vc6.0下编译运行结果为:8 20 32.
能够分析出codeblocks下的结果是本类加上父类的结果。vc下是 本类加父类后又加了4个字节。

但是这段代码:
#include<iostream>
using namespace std;
class A
{

public:
virtual void a(){};
};
class B:public virtual A
{

public:
virtual void b(){};

};
class C:public virtual B
{

public:
virtual void c(){};
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
}
codeblocks下编译运行结果为 4 4 4.
vc下编译运行结果为:4 12 20.
从结果来看,vc下是 本类加父类后又加了4个字节。而codeblocks结果就不再是本类加父类的结果(这个有点迷茫)。

C++虚表问题
对于一个类如果有虚函数,就会在这个类中创建一个虚表,也就会产生一个虚指针指向这个虚表。既然有一个指针指向了虚表,这个类派生后,在派生类中就不必再创建虚表,如果派生类还有自己的虚函数,那么只在派生类中创建该虚函数的一个虚表,产生一个指向该虚表的指针。为每个类设置虚表,初始化虚指针,为...

C++中虚函数的作用和虚函数的工作原理
虚函数在C++中实现多态性,主要作用在于实现动态绑定。基类定义虚函数,子类可以重写该函数。当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,程序根据对象的实际类型动态调用子类的该函数,而不是父类的函数。这种动态调用发生在运行阶段,称为动态联编。非虚函数的静态联编效率更高,...

C++中是不是每个类(有虚函数)都对应一个virtual function table?_百...
你理解的是对的,子类和父类各有一个虚函数表,并且虚函数指针也是指向各自的。子类先是从父类复制了一个虚函数表,如果子类对父类的虚函数进行了覆盖,则在子类的虚函数表将会用子类的函数地址覆盖父类的,如果没有覆盖,则还是使用父类的函数地址,这样就实现了多态。

关于C++ Virtual 关键字的一切(1):虚函数的内部原理是什么?
在C++中,"关于C++ Virtual关键字的一切(1):虚函数的内部原理"系列文章深入剖析了虚函数的核心机制,特别是动态调度(dynamic dispatch)。文章以连接手机网络的比喻展开,解释了为何需要这种功能,即在抽象层面上,不同的通信协议(如Wifi和蓝牙)需要共享一些通用步骤,如身份认证和连接。虚函数的关键在...

C++中虚继承和一般的继承有什么不同
虚继承,Class A和Class B的关系就会微妙很多。由于C++支持多继承,所以某些情况下会出现下图中的继承关系。这种水晶继承会导致Class D中包含两份Class A的对象。此时就会出现访问歧义的情况。虚继承就可以避免上面的情况。Class A的数据会被放到虚表中。Class D会识别到来自Class B和C的虚表,然后将两者...

多重继承中,每个虚表第一个槽中的type_info是对应basecla
在C++的多重继承中,每个类可以继承自多个基类。然而,内存布局中,基类的顺序在继承时会直接影响虚表的生成。虚表是类的元数据集合,用于存储指向虚函数的指针,是实现多态的关键。每个类的虚表都会被分配在对象的内存布局中,通常位于对象开头的固定位置。虚表的第一个槽用于存放特定类型的信息,而第二...

C++基类子类中,虚函数究竟是怎样判断该调用哪个函数的??
有虚函数时,每个对象的this指针都指向一个虚函数表(Virtual Table)的地址,这个表里存的就是虚函数的地址。编译的时候就决定了,普通函数调用时直接CALL这个函数的地址,而是虚函数时,是从这个虚表里取地址去调用的。

C++中为什么构造函数不能是虚函数,析构函数是虚函数
也即不能完成父类的构造.就会出错.析构函数在某些情况下必须为虚函数(比如你想让你类能够被继承,那么这个类的析构函数最好是虚的,继承一个析构函数不是虚的类是有风险的),值得补充的是,当基类的函数是虚函数,子类的重载的函数也是虚的,即使不加virtual关键字也是虚的。

C++的virtual到底是怎么作用的,它在内部是怎么处理的?
virtual就是告诉编译器这是一个虚函数 编译器就会延迟绑定,在运行时从虚表里找到这个A*指针真正的对象是B类型,从而调用B的fun函数。你可以读一下《深入探索C++对象模型》,此书不读你就不是搞C++的!

c++ 取地址直接就得到地址了 为啥还要强制转换成int* 这不就变成指针类...
b的类型是Base,&b就是Base*型了,第一个cout里面的确可以不用转换成int*,不过为了防止后面的出错和保持一致性,建议习惯这样的写法,理由是假定你需要输出第二虚函数的地址(*((int*)*(int*)&b+1))你需要地址+1,如果不转换成int*,可能会存在+1后偏移值不正确,虚表中都是地址,与Base的...

相似回答