我很新,所以对我很轻松:) 从我讲师前面讲过的内容来看,虚拟表的顺序非常重要。 但我不明白这个原因!!?
给出下一个代码:
class A
{
public:
A() {cout <<"1" << endl;};
A (const A& s) {cout << "2" << endl;}
~A () {cout << "3" << endl;}
void f1() {cout << "4" << endl; f2();}
virtual void f2() = 0;
virtual void f3() {cout << "5" << endl;}
};
class B : public A
{
public:
B() {cout << "6" << endl;}
B(const B& b) : A(b) {cout << "7" << endl;}
~B() {cout << "8" << endl;}
virtual void f1() {cout<<"9"<<endl;}
void f2() {cout<<"lO"<<endl; f4();}
virtual void f2(int i) {cout << "11" << endl;}
virtual void f4() {cout << "12" << endl; f3();}
};
他说订单是:
A's vtable :
A::f2()
A::f3()
B's vtable :
B::f2()
A::f3()
B::f1()
B::f2(int)
B::f4()
但我不明白为什么这很重要?他说,如果是的话,vtable就没用了 不是按照正确的顺序,你能解释一下原因吗?
答案 0 :(得分:17)
C ++标准中没有vtable的概念。只是大多数实现(如果不是全部)都将它用于虚拟调度。但是,确切的约定完全是实现定义的。
那就是说...函数的顺序很重要,但不是程序员,而是编译器 - 你可以在你的代码中安排你想要的函数。但是,编译器通常会将每个函数指针放入vtable中的特定位置,该位置专用于该函数。因此,当它需要调用f()
时,它知道f()
函数的索引并从vtable中获取该指针。
这个问题也可能对您有所帮助:Virtual dispatch implementation details
答案 1 :(得分:9)
vtable的顺序对于正常工作很重要,但仅限于编译器(即你不需要关心,因为它会处理它)。
如果编译器把它自己搞定了,那么是的,事情会破坏,因为函数是通过偏移查找的(所以偏移会产生一个随机函数,这将是灾难性的)。但普通的程序员不会需要担心vtable的订单。
答案 2 :(得分:7)
当类声明外部ABI的接口(例如COM / XPCOM)时,这一点很重要。
大部分时间它并不重要,没有理由关心它。
答案 3 :(得分:2)
vtable的每个客户都需要知道正确的顺序,以便他们能够找到正确的调用方法。但只要各方就订单达成一致,那订单是什么并不重要。
答案 4 :(得分:1)
vtable是一个“查找”表。它基本上是指向类的虚函数的指针映射。如果它出现故障,指针将指向错误的功能。如果您想要调用B:f1()
,而不需要参数,而是调用B::f2()
,则需要int
。
答案 5 :(得分:0)
我不确定他的意思,但我会尝试解释它的工作原理:
首先,c ++按名称和签名定义方法。因此,当c ++启动类的虚拟表时,它将使用具有相同名称和签名的派生虚函数替换所有基类的虚函数。
当一个类派生另一个类时,它实际构建在它上面。因此基类作为内存块的一部分存在(复杂,在此处阅读 - Virtual inheritance )
虚拟表只是在运行时根据类型保存到右侧函数的“指针”。
答案 6 :(得分:0)
在Visual Studio中,当您更改虚拟函数的声明顺序时,您可能需要随后清理并重建整个解决方案。
我的猜测是,vtable在特定情况下会不同步。
因此,如果在对虚拟函数进行更改后事情变糟了,那么只需重新编译就可以解决问题。