考虑以下示例代码:
class Base {
public:
void f();
virtual void vf();
};
class Derived : public Base {
public:
void f();
void vf();
};
#include <iostream>
using namespace std;
void Base::f() {
cout << "Base f()" << endl;
}
void Base::vf() {
cout << "Base vf()" << endl;
}
void Derived::f() {
cout << "Derived f()" << endl;
}
void Derived::vf() {
cout << "Derived vf()" << endl;
}
int main()
{
Base b1;
Derived d1;
b1.f();
b1.vf();
d1.f();
d1.vf();
Derived d2; // Derived object
Base* bp = &d2; // Base pointer to Derived object
bp->f(); // Base f()
bp->vf(); // which vf()?
return 0;
}
运行的输出是:
Base f()
Base vf()
Derived f()
Derived vf()
Base f()
Derived vf()
问题:
在
Base* bp = &d2
行中,对象类型在编译时是已知的。那么在bp->vf();
的情况下使用哪个函数的决定也可以在编译时做出,对吗?既然对象类型在编译时本身就已知,那么在这个示例程序中是否使用了虚函数的功能?
在
Base* bp = &d2
行,对象类型在编译时是已知的。那么在bp->vf();
的情况下使用哪个函数的决定也可以在编译时做出,对吗?
是的。
这是编译器优化的一部分,大多数现代智能编译器都能做到。
如果编译器可以在编译时自己决定调用哪个函数,那么它就会这样做。虽然这完全取决于编译器是否能够在编译时或运行时检测到要调用的确切函数,但虚拟主义可以保证您的程序具有您想要的行为。
既然对象类型在编译时本身就已知,那么在这个示例程序中是否使用了虚函数的功能?
完全取决于编译器。大多数现代编译器将能够在编译时计算此函数调用。
这个程序很简单,并没有很好地展示虚函数(或者更一般地说,多态性)的强大功能。考虑以下更改:
// add second derived class
class Derived2 : public Base {
public:
void vf() { std::cout << "Derived2 vf()" << std::endl; }
};
// in main
int user_choice;
std::cin >> user_choice;
Base * ptr;
if (user_choice == 0)
ptr = new Derived();
else
ptr = new Derived2();
ptr->vf();
在这里,类的选择取决于用户输入——编译器无法预测调用ptr->vf();
时ptr
实际指向的对象类型。
嗯…对这两个问题的回答都是"是"one_answers"否":这取决于你所争论的抽象层次。
-
从语言的角度来看,1)是一种误解。
d2
的类型是已知的,但是当将d2地址分配给bp
时,将发生从Derived*
到Base*
的转换。从那时起,bp的静态类型是Base*
(因为这是它的声明),它指向的动态类型是派生的(因为这是与对象关联的运行时类型信息所引用的)。从此以后,通过bp的所有操作都假定Base*为类型,并且每个虚函数都需要重定向。 -
从编译器的角度来看,可以进行某些优化,并且由于在所有函数中pb总是指向派生函数,因此实际上可以跳过虚拟重定向。但这是由于您的特定示例的结构方式,而不是因为语言特性。
In the line Base* bp = &d2, the object type is known at compile time. Then the decision of which function to use in the case of bp->vf(); can also be made at compile time right?
不,使用哪个函数的决定是在运行时根据对象的类型动态完成的,而不是根据指向该对象的指针/引用的类型。这被称为动态绑定。编译器保留一个名为virtual pointer
或vptr
的隐藏指针,该指针指向一个称为虚拟表的表。每个类将有一个虚拟表,其中至少有一个虚拟函数(不管为该类创建了多少对象)。虚表包含类的虚函数的地址。
Since the object type is known at compile time itself, is the power of virtual functions used in this sample program?
实际上,所指向的对象在编译时可能不知道。以基类指针为参数的方法为例,如下所示:
void draw(Shape *bp)
{
bp->draw();
}
在这种情况下,实际对象可能是来自Shape
的任何形状。但是绘制的形状取决于传入对象的实际类型。