我在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 << "An"; }
};
struct B : A {
CONDITIONAL_VIRTUAL void foo() { std::cout << "Bn"; }
};
// 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();
}
该结合与CCD_ 2的评价有关。编译器必须弄清楚该表达式要执行什么代码。对于静态绑定和动态绑定,编译器查看x
并查看其类型为A*
,因此查看struct A
并查找foo()
声明。
使用静态绑定时,编译器会发现foo()
不是virtual
,因此编译器会继续生成调用该foo()
方法的代码。易于理解的
使用动态绑定,编译器会看到标记为virtual
的方法,因此编译器会生成代码,在运行时,该代码将使用与对象x
相关的函数指针表来选择要调用的方法,然后调用找到的任何方法。编译器还在其他地方生成代码,为全局a
和b
对象创建表。对于全局a
对象,它使表指向A::foo()
,而对于全局b
,它使表格指向B::foo()
。因此,如果x
指向b
对象,那么表查找将导致x->foo()
0,这就是将被调用的函数。
通常,编译器必须确保所有具有虚拟方法的对象都有一个表,该表指向要调用的正确函数,这样,每当对对象进行虚拟调用时,程序都可以在运行时获取与该对象关联的表,并查找要调用的正确方法。
因此,在静态和动态模式下构建上述程序,然后运行它并观察每个输入的输出。在下表中填写输入和绑定类型的每种组合的输出。
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();
}