C++ 虚拟表的顺序重要吗?

C++ 虚拟表的顺序重要吗?,c++,C++,我是新来的,所以对我放轻松:) 根据我的讲师不久前所说的,虚拟表的顺序很重要。 但我不明白原因 给出下一个代码: class A { public: A() {cout <<"1" << endl;}; A (const A& s) {cout << "2" << endl;} ~A () {cout << "3" << endl;} void f1() {cout <<

我是新来的,所以对我放轻松:) 根据我的讲师不久前所说的,虚拟表的顺序很重要。 但我不明白原因

给出下一个代码:

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();}

};
但我不明白为什么它很重要?他说vtable是无用的,如果它是
不是按正确的顺序排列的,您能解释一下原因吗?

vtable是一个“查找”表。它基本上是指向类的虚拟函数的指针的映射。如果出现故障,指针将指向错误的函数。例如,如果您想调用
B:f1()
,它不带参数,而是调用
B::f2()
,它带一个
int
,,则会发生不好的事情。vtable的顺序对正常工作很重要,但仅对编译器很重要(即,您不需要在意,因为它会在意它)


<>如果编译器把它自己放错了,那么,事情就会中断,因为函数是用偏移量查找的(所以偏移量会产生一个随机函数,这将是灾难性的)。但是一般程序员不必担心VTAT的顺序。

< P> C++中没有VTABLE的概念。只是大多数实现(如果不是全部的话)都将其用于虚拟调度。然而,确切的约定是完全由实现定义的

也就是说。。。函数的顺序很重要,但不是对程序员来说,而是对编译器来说——您可以在代码中根据需要排列函数。然而,编译器通常会将每个函数指针放在vtable中的一个特定位置,它专用于该函数。因此,当它需要调用
f()
时,它知道
f()
函数的索引,并从vtable获取该指针


这个问题也可能对您有所帮助:

vtable的每个客户端都需要知道正确的顺序,以便找到正确的调用方法。但只要各方同意顺序,顺序是什么并不重要。

只有当类为外部ABI(例如COM/XPCOM)声明接口时才重要


大多数时候,这并不重要,也没有理由去关心它。

我不确定他是什么意思,但我会尝试解释一下它是如何工作的:

首先,C++通过名称和签名定义方法。因此,当C++启动类的虚拟表时,它将用派生的虚拟函数替换所有基类的虚函数,并具有相同的名称和签名。 当一个类派生另一个类时,它实际上是在它上面构建的。因此基类作为内存块的一部分存在(复杂,请阅读此处-


根据运行时的类型,虚拟表只保留一个指向正确函数的“指针”。

在visual studio中,当您更改虚拟函数声明的顺序时,可能需要随后清理并重新生成整个解决方案

我的猜测是vtable在特定情况下不同步


因此,如果在更改虚拟函数后出现问题,只需重新编译就可以解决问题。

+1 Windows平台上vtable布局成为标准的唯一原因是COM。作为编译器供应商,如果您想支持COM,必须遵循Microsoft的实现。但是,COM未使用的更高级功能,如虚拟继承或非接口的多重继承,则依赖于实现。@Ron\u s如果回答了您的问题,请确保单击此答案左上角的复选标记。讲师是否告诉过您
A
B
的主要基础?
A's vtable : 
A::f2()
A::f3()

B's vtable : 
B::f2()
A::f3()
B::f1()
B::f2(int)
B::f4()