C++ 为什么我们说虚拟函数的调用不能在编译时确定?
可能重复:C++ 为什么我们说虚拟函数的调用不能在编译时确定?,c++,C++,可能重复: 我们说C++中虚函数的调用是在运行时确定的,而不是在编译时。所以我想我不清楚编译时和运行时之间的区别。在我看来,一切都应该在编译时确定。。。谁能在这个问题上提供帮助?谢谢!通常,您不知道调用虚拟函数时将执行什么代码: struct Base { virtual void method() = 0; }; void foo(Base* p) { p->method(); // What code will be execute here? } 如果有多个
<>我们说C++中虚函数的调用是在运行时确定的,而不是在编译时。所以我想我不清楚编译时和运行时之间的区别。在我看来,一切都应该在编译时确定。。。谁能在这个问题上提供帮助?谢谢!通常,您不知道调用虚拟函数时将执行什么代码:
struct Base
{
virtual void method() = 0;
};
void foo(Base* p)
{
p->method(); // What code will be execute here?
}
如果有多个类派生自Base
,将执行哪些代码
class C
{
public:
virtual void f() {}
};
class D : public C
{
public:
void f() {}
};
void fn(C * c)
{
// Is C::f or D::f called here?
c->f();
}
结构基{virtual void foo(){std::cout标志){
基本*ptr=标志?&b:&d;
ptr->foo();
}否则{
简言之,std::cout与上述内容一致
编译时是源代码编译的时间,运行时是编译代码执行的时间,这可能取决于您对程序的输入……,因此,根据您的输入,在运行时决定哪个对象引用将访问虚拟函数 有调用的事实是在编译时确定的。只有在对象已知时才能知道调用的成员函数。例如
Base* ptr = (rnd() % 2 ? new D1() : new D2());
ptr->vf();
如果
vf()
struct Base
{
virtual void func()
{ std::cout << "Base::func()" << std::endl; }
};
struct Derived : Base
{
virtual void func()
{ std::cout << "Derived::func()" << std::endl; }
};
编译器无法决定是在bp->func()
中调用基类的函数还是派生类的函数,因为它取决于来自用户的输入
这说明了编译时和运行时之间的区别:编译器在编译时将代码转换为机器代码,但用户输入仅在运行时可用
(我的代码示例不是完美的代码。例如,我使用虚拟函数声明类,但没有声明虚拟析构函数。还有其他问题。这只是为了说明编译时和运行时之间的区别,并说明每次都有哪些是可能的,哪些是不可能的。)在任何给定函数中,在编译时,无论函数的输入参数值如何,您只能推断函数在任何可能的运行中都是正确的。无论何时输入函数的数据可能改变其行为,您都无法在编译时推导出结果
例如:
class A
{
virtual void virt() = 0;
};
class B : public A
{
virtual void virt() { /*some computation */};
};
class C : public A
{
virtual void virt() { /*some other computation */};
};
void f(A* a)
{
a->virt();
}
在这里,当我们编译f
时,无法知道a
指向的对象是否属于B
或C
类型,或者我们甚至不知道的其他派生类型(在此编译单元中)。事实上,这可能取决于用户输入,并且可能会因每次运行而有所不同
因此,在编译时,当我们键入a->virt()
时,我们不知道实际将调用什么函数
然而,在运行时中,我们确实知道a
实际指向什么,因此我们可以确定将调用哪个函数
实际上,虚拟函数调用是使用vtable来解决的,vtable是指向类中所有虚拟函数的指针数组。在技术层面上(希望我能直截了当地了解事实),有一种称为vtable的东西是为实现多态性而构建的
基本上,每个类只能有一个vtable,因此类的任何实例都将共享相同的vtable,vtable可以说是程序员看不见的,它包含指向虚拟函数实现的指针
是编译器构建了vtable,并且只有在需要时才构建它们(即,如果类或其基类包含虚拟函数
),因此值得注意的是,并非所有类都构建了vtable
示例时间:
class Base {
public:
virtual void helloWorld();
}
class Derived : public Base {
public:
void helloWorld();
}
int main(void) {
Derived d;
Base *b = &d;
b->helloWorld(); // here is the magic...
/* This call is actually translated to something like the line below,
lets assume we know that the virtual pointer pointing to the viable
for Derived is called Derived_vpointer (but it's only a name and
probably not what it would be called):
*(b -> Derived_vpointer -> helloWorld() )
*/
这意味着当b->helloWorld()时
被称为它实际上使用一个vpointer查找一个vtable,该vtable在中被替换以引导调用到虚拟函数的正确版本。因此这里的类派生的有一个vtable和一个指向表的虚拟指针。因此当b指向Derived实例它将使用Derived中的vpointer调用正确的实现
这是在运行时完成的,或者说查找是在运行时完成的,因为我们可以很容易地让另一个类扩展Base,并使b
指向这个类(让我们称之为另一个派生的)类
是另一个派生的的vpointer将用于评估对helloWorld()的调用
所以让我们把它写进代码里
...
int main(void) {
Derived derived;
AnotherDerived anotherDerived;
Base *base;
base->helloWorld();
/* base points to a Base object, i.e. helloWorld() will be called for base. */
*base = &derived; // base's vpointer will point at the vtable of Derived!
base->helloWorld();
/* calling:
base->Derived_vpointer->helloWorld();
*/
*base = &anotherDerived;
base->helloWorld(); // base's vpointer will point at the vtable of AnotherDerivedClass
/* calling:
base->AnotherDerived_vpointer->helloWorld();
*/
class Base {
public:
virtual void helloWorld();
}
class Derived : public Base {
public:
void helloWorld();
}
int main(void) {
Derived d;
Base *b = &d;
b->helloWorld(); // here is the magic...
/* This call is actually translated to something like the line below,
lets assume we know that the virtual pointer pointing to the viable
for Derived is called Derived_vpointer (but it's only a name and
probably not what it would be called):
*(b -> Derived_vpointer -> helloWorld() )
*/
...
int main(void) {
Derived derived;
AnotherDerived anotherDerived;
Base *base;
base->helloWorld();
/* base points to a Base object, i.e. helloWorld() will be called for base. */
*base = &derived; // base's vpointer will point at the vtable of Derived!
base->helloWorld();
/* calling:
base->Derived_vpointer->helloWorld();
*/
*base = &anotherDerived;
base->helloWorld(); // base's vpointer will point at the vtable of AnotherDerivedClass
/* calling:
base->AnotherDerived_vpointer->helloWorld();
*/