为什么我们说虚拟函数的调用不能在编译时确定
Why do we say that the call of virtual function cannot be determined in compiling time?
可能重复:
如果虚拟表是在编译时创建的,那么为什么我们将其称为运行时多态性呢?
我们说C++中虚拟函数的调用是在运行时决定的,而不是在编译时。所以我想我不清楚编译时和运行时之间的区别。在我粗略的想法中,一切都应该在编译时确定。。。谁能在这个问题上帮忙?谢谢
看看这个简化的例子:
struct Base
{
virtual void func()
{ std::cout << "Base::func()" << std::endl; }
};
struct Derived : Base
{
virtual void func()
{ std::cout << "Derived::func()" << std::endl; }
};
有一个带有虚拟函数的基类,以及一个重写它的派生类
int main()
{
Base *bp = 0;
std::string input;
std::cin >> input;
if (input == "base")
bp = new Base;
else
bp = new Derived;
/* The compiler cannot decide which
function is called here: */
bp->func();
return 0;
}
编译器无法决定在bp->func()
中调用基类的函数还是派生类的函数,因为这取决于来自用户的输入。
这说明了编译时和运行时之间的区别:编译器在编译时将代码转换为机器代码,但用户输入仅在运行时可用。
(我的代码示例不是完美的代码。例如,我用虚拟函数声明类,而没有声明虚拟析构函数。还有其他问题。这只是为了说明编译时和运行时之间的区别,并显示每次什么是可能的,什么是不可能的。(。)
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();
}
存在调用的事实在编译时确定。只有当对象已知时,才能知道调用的成员函数。例如在中
Base* ptr = (rnd() % 2 ? new D1() : new D2());
ptr->vf();
如果vf()
是一个虚拟函数,那么您就不知道在编译时调用哪个vf()
(假设D1和D2各有自己的(。
在任何给定的函数中,在编译时,无论其输入参数的值如何,您只能推断出在函数的任何可能运行中都是正确的。每当输入到函数的数据可能改变其行为时,您就无法在编译时推导出结果。
例如:
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
#include <iostream>
struct Base { virtual void foo() { std::cout << "basen"; } };
struct Derived : Base { void foo() { std::cout << "derivedn"; } };
int main() {
Base b;
Derived d;
bool flag;
if (std::cin >> flag) {
Base *ptr = flag ? &b : &d;
ptr->foo();
} else {
std::cout << "errorn";
}
}
我不知道你目前认为的"编译时"是什么,但程序编译和链接都发生在程序运行之前,因此也发生在用户提供输入之前。因此,调用foo
的目的地不可能在编译时确定,因为它取决于运行时的用户输入。
如果foo
是一个非虚拟函数,那么无论flag
的值如何,都会调用Base::foo()
,因此在这种情况下,目的地在编译时是已知的。
通常,当您调用虚拟函数时,您不知道将执行什么代码:
struct Base
{
virtual void method() = 0;
};
void foo(Base* p)
{
p->method(); // What code will be execute here?
}
如果从Base
派生出多个类,将执行什么代码?
以及以上内容,简而言之编译时间是编译源代码的时间,运行时间是执行编译代码的时间[/strong>,这可能取决于您对程序的输入。。。。,因此,根据您的输入,可以在运行时决定哪个对象引用将访问虚拟函数。此处
在技术层面上(希望我能澄清事实:S(,有一种叫做vtable的东西,它是为实现多态性而构建的。
基本上,每个类只能有一个vtable,因此类的任何实例都将共享相同的vtabletable对程序员来说是不可见的,并且它包含指向虚拟函数实现的指针。
是编译器构建vtables,并且只有在需要时才会构建它们(即,如果类或其基类包含virtual function
。因此,值得注意的是,并非所有类都会构建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,以将调用引导到虚拟函数的正确版本。因此,这里的类Derived
具有指向该表的vtable和虚拟指针。因此,当b指向派生实例时,它将使用衍生中的vpointer,最终调用正确的实现。
这是在运行时完成的,或者更确切地说,查找是在运行时完成的,因为我们可以很容易地让另一个类扩展Base并使b
指向这个(让我们称之为AnotherDerived
(类。当我们再次使用b->helloWorld()
时,会发生AnotherDerived
的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();
*/
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 为什么我的 EnumWindowProc 不能用 C 语言编译?
- std::unique_ptr 在 GCC 中工作,但不能在 Visual Studio 中编译
- 为什么通过指针编译时不能分配 const 初始化
- std::variant<>::get() 不能使用 Apple LLVM 10.0 编译
- 为什么使用 std::vector 的代码不能编译,但使用 std::unique_ptr 如果没有 noexcept
- 我不能用C++编译Xcode库
- 返回实例变量的c++方法可以访问变量中的数据,但不能更改它,但在编译时不会生成错误
- 为什么引用不能与编译时函数一起使用?
- 为什么我不能编译这个在Cygwin的Visual Studio中编写的C++文件?
- 为什么一个简单的"Hello World"风格的程序不能用Turbo C++编译?
- 为什么我不能编译这个简单的线程测试?
- C++程序使用 mingw 在 Linux 上交叉编译在 MSy2 中工作,但不能直接在 Windows 中工作
- 此模板函数在哪里生成?可以通过g++编译,但不能在Visual Studio中编译
- 当 pkg-config 不能解决 opencv 'undefined reference'编译问题时,如何解决它?
- 没有返回类型的静态函数可以通过Windows上的编译,但不能传递Linux上的编译
- 警告:不能解析.gnu_debugdata部分;LZMA支持在编译时被禁用
- 为什么 --string::end() 可以编译,而 --string.size() 不能编译?
- lower_bound()函数不能在调试模式下编译
- QT-不能使用外部库进行交叉编译