我在 Linux 上使用 GCC。
我想了解工作中的虚拟功能。
我应该编写什么样的 C++ 代码来查看和理解使用和不使用虚拟函数时静态和动态绑定是如何发生的?
以及如何“看到”它们最终是如何被绑定的,以及在这个过程中究竟发生了什么?
我在 Linux 上使用 GCC。
我想了解工作中的虚拟功能。
我应该编写什么样的 C++ 代码来查看和理解使用和不使用虚拟函数时静态和动态绑定是如何发生的?
以及如何“看到”它们最终是如何被绑定的,以及在这个过程中究竟发生了什么?
这是一个例子。您可以使用设置为虚拟函数或不设置为虚拟函数的函数来构建和运行此代码。要获得虚拟行为、动态调度、动态绑定,请使用定义的预处理器宏构建它IS_VIRTUAL
。要查看静态绑定,请在不定义该宏的情况下构建它。
#include <iostream>
#if defined(IS_VIRTUAL)
#define CONDITIONAL_VIRTUAL virtual
#else
#define CONDITIONAL_VIRTUAL
#endif
struct A {
CONDITIONAL_VIRTUAL void foo() { std::cout << "A\n"; }
};
struct B : A {
CONDITIONAL_VIRTUAL void foo() { std::cout << "B\n"; }
};
// global objects
A a; B b;
enum object_type { get_A, get_B };
A *get_object(object_type t) {
switch (t) {
case get_A: return &a;
case get_B: return &b;
}
}
int main() {
std::cout << "Choose A or B: ";
char c;
std::cin >> c;
A *x = get_object( c == 'A' ? get_A : get_B );
x->foo();
}
绑定与 的评估相关x->foo()
。编译器必须弄清楚要为该表达式执行什么代码。对于静态和动态绑定,编译器会查看x
并看到它的类型 is A*
,因此它会查看struct A
并查找foo()
声明。
使用静态绑定,编译器会发现foo()
is not virtual
,因此编译器会继续生成调用该foo()
方法的代码。简单的。
使用动态绑定,编译器会看到标记为 的方法virtual
,因此编译器会生成代码,该代码将在运行时使用与对象关联的函数指针表x
来选择要调用的方法,然后调用找到的任何方法。编译器还在其他地方生成代码来为全局a
和b
对象创建表。对于全局a
对象,它使表指向A::foo()
,而对于全局对象,b
它使表指向B::foo()
。因此,如果x
指向b
对象,则将导致查找表B::foo()
,这就是将被调用的函数。
一般来说,编译器必须确保所有具有虚方法的对象也有一个表,该表指向要调用的正确函数,以便任何时候对对象进行虚调用,程序都可以在运行时获取与对象关联的表并查找要调用的正确方法。
因此,在静态和动态模式下构建上述程序,然后运行它并观察每个输入的输出。在下表中填写您为每种输入和绑定类型组合获得的输出。
Binding | static dynamic
Input
-----
A ? ?
B ? ?
在所有情况下,输出都是通过对同一x->foo()
方法调用的评估产生的。在哪些情况下具有动态约束力?这是否与您对上述动态绑定的解释的理解相符?
class Base {
public:
int Foo();
virtual int Bar();
};
class D1 : public Base {
public:
int Foo();
virtual int Bar();
};
class D2 : public Base {
public:
int Foo();
virtual int Bar();
};
main()
{
Base * b = (rand() < 100) ? new D1 : new D2;
// Always calls Base::Foo()
b->Foo();
// Call either D1::Bar() or D2::Bar()
b->Bar();
}