多继承中vtable c++中保存的重写虚方法在哪里
where is the overridden virtual method saved in the vtable c++ in multiple inheritance
在 C++ 中,运行 时没有 class 表示,但我总是可以在派生的 class 中调用重写的虚拟方法。 vtable 中保存的重写方法在哪里?这是一段代码来演示:
struct B1 {
virtual void f() { ... }
};
struct B2 {
virtual void f() { ... }
virtual void g() { ... }
};
struct D : B1, B2 {
void f() { ... }
virtual void h() { ... }
};
classD 对象的内存布局是什么? B1::f 和 B2::f 保存在那个内存布局中的什么位置(如果它们被保存的话)?
Class D
的对象 d
将只有一个指向 class D
的 VMT 的指针,它将包含指向 D::f 的指针。
由于 B1:f 和 B2::f 只能从 D class 的范围内静态调用,因此对象 d
不需要保留指向那些被覆盖方法的动态指针。
这就是标准中没有定义的原因,这只是编译器的usual/logical实现。
其实情况更复杂,因为class D的VMT合并了classes B1和B2的VMT。但是无论如何,在创建class B1的对象之前,不需要动态调用B1::f。
当编译器使用virtual dispatch*的vtable方法时,重写成员函数的地址存放在基类的vtable中class其中定义了函数。
每个 class 都可以访问其所有基数 class 的 vtable。这些 vtable 存储在 class 本身的内存布局之外。每个具有虚拟成员函数的 class,声明的或继承的,都有一个指向它自己的 vtable 的指针。当您调用覆盖的成员函数时,您需要提供要调用其成员函数的基 class 的名称。编译器知道所有 classes 的 vtable,它知道如何定位基 class 的 vtable,在编译时进行查找,并且直接调用成员函数
这是一个简短的例子:
struct A {
virtual void foo() { cout << "A"; }
};
struct B : public A { }; // No overrides
struct C : public B {
virtual void foo() { cout << "C"; }
void bar() { B::foo(); }
};
在上面的示例中,编译器需要查找 B::foo
,它未在 class B
中定义。编译器查询其符号 table 发现 B::foo
是在 A
中实现的,并在 C::bar
.
中生成对 A::foo
的调用
* vtables 不是实现虚拟调度的唯一方法。 C++ 标准不要求使用 vtables。
虽然 C++ 标准没有强制要求,但每个已知的 C++ 实现都使用相同的方法:每个 class 至少有一个虚函数都有一个 vptr(指向 vtable 的指针)。
你没有提到虚拟继承,这是一种不同的、更微妙的继承关系;非虚拟继承是基础 class 子对象和派生 class 之间的简单排他关系。 我会假设在这个答案中所有的继承关系都不是虚拟的。
这里我假设我们派生自 classes 至少有一个虚函数。
在单继承的情况下,来自基础 class 的 vptr 被重用。 (不重复使用它只会浪费 space 和 运行 时间。)基础 class 称为“主要基础 class”。
在多重继承的情况下,派生class的布局包含每个基class的布局,就像C中结构的布局包含每个成员的布局一样。 D
的布局是 B1
然后 B2
(实际顺序是任意的,但通常保持源代码顺序)。
第一个 class 是主基 class:在 D
中,来自 B1
的 vptr 指向 D
的完整 vtable,vtable具有 D
的所有虚函数。来自非主库 class 的每个 vptr 指向 D
的辅助 vtable:一个仅包含来自该辅助库 class.
的虚函数的 vtable
D
的构造函数必须初始化 class 实例的每个 vptr 以指向 D
.
的适当 vtable
在 C++ 中,运行 时没有 class 表示,但我总是可以在派生的 class 中调用重写的虚拟方法。 vtable 中保存的重写方法在哪里?这是一段代码来演示:
struct B1 {
virtual void f() { ... }
};
struct B2 {
virtual void f() { ... }
virtual void g() { ... }
};
struct D : B1, B2 {
void f() { ... }
virtual void h() { ... }
};
classD 对象的内存布局是什么? B1::f 和 B2::f 保存在那个内存布局中的什么位置(如果它们被保存的话)?
Class D
的对象 d
将只有一个指向 class D
的 VMT 的指针,它将包含指向 D::f 的指针。
由于 B1:f 和 B2::f 只能从 D class 的范围内静态调用,因此对象 d
不需要保留指向那些被覆盖方法的动态指针。
这就是标准中没有定义的原因,这只是编译器的usual/logical实现。
其实情况更复杂,因为class D的VMT合并了classes B1和B2的VMT。但是无论如何,在创建class B1的对象之前,不需要动态调用B1::f。
当编译器使用virtual dispatch*的vtable方法时,重写成员函数的地址存放在基类的vtable中class其中定义了函数。
每个 class 都可以访问其所有基数 class 的 vtable。这些 vtable 存储在 class 本身的内存布局之外。每个具有虚拟成员函数的 class,声明的或继承的,都有一个指向它自己的 vtable 的指针。当您调用覆盖的成员函数时,您需要提供要调用其成员函数的基 class 的名称。编译器知道所有 classes 的 vtable,它知道如何定位基 class 的 vtable,在编译时进行查找,并且直接调用成员函数
这是一个简短的例子:
struct A {
virtual void foo() { cout << "A"; }
};
struct B : public A { }; // No overrides
struct C : public B {
virtual void foo() { cout << "C"; }
void bar() { B::foo(); }
};
在上面的示例中,编译器需要查找 B::foo
,它未在 class B
中定义。编译器查询其符号 table 发现 B::foo
是在 A
中实现的,并在 C::bar
.
A::foo
的调用
* vtables 不是实现虚拟调度的唯一方法。 C++ 标准不要求使用 vtables。
虽然 C++ 标准没有强制要求,但每个已知的 C++ 实现都使用相同的方法:每个 class 至少有一个虚函数都有一个 vptr(指向 vtable 的指针)。
你没有提到虚拟继承,这是一种不同的、更微妙的继承关系;非虚拟继承是基础 class 子对象和派生 class 之间的简单排他关系。 我会假设在这个答案中所有的继承关系都不是虚拟的。
这里我假设我们派生自 classes 至少有一个虚函数。
在单继承的情况下,来自基础 class 的 vptr 被重用。 (不重复使用它只会浪费 space 和 运行 时间。)基础 class 称为“主要基础 class”。
在多重继承的情况下,派生class的布局包含每个基class的布局,就像C中结构的布局包含每个成员的布局一样。 D
的布局是 B1
然后 B2
(实际顺序是任意的,但通常保持源代码顺序)。
第一个 class 是主基 class:在 D
中,来自 B1
的 vptr 指向 D
的完整 vtable,vtable具有 D
的所有虚函数。来自非主库 class 的每个 vptr 指向 D
的辅助 vtable:一个仅包含来自该辅助库 class.
D
的构造函数必须初始化 class 实例的每个 vptr 以指向 D
.