为什么在此示例中需要后期绑定?

时间:2020-05-22 16:01:17

标签: c++ oop pointers binding compiler-construction

我了解为什么在动态创建子类的对象时需要使用virtual关键字,但是在下面的示例中,为什么要覆盖后期绑定(virtual关键字)?编译器无法在编译时告知pa指向派生类吗?

class A { 
    public: int f() { return 'A';}
};

class B : public A {
    public: int f() { return 'B';} 
};

int main() {

    //I understand this case, since pa is pointing to 
    //something created at runtime, virtual keyword is needed
    //to return 66 
    A* pa;
    pa = new B;
    cout << pa->f() << endl; //returns 65 

    //But why in this case where b2 is created during 
    //compile time, compiler doesn't know pa is pointing
    //to a derived class? 
    B b2; 
    pa = &b2; 
    cout << pa->f() << endl; //returns 65
}

3 个答案:

答案 0 :(得分:3)

这个问题实际上并没有围绕编译器是否可以“知道”对象pa所指的确切类型而展开。它围绕C ++的语义展开。

在声明方法f时,您在告诉编译器如何处理对f的调用。如果未声明A::f virtual,则表示如果调用pa->f()并且pa的声明类型为A*,则需要编译器以使用A::f中的定义。当然,*paA类型的对象,即使它也可能是A的某些派生类型的对象。

反之,如果您将f声明为virtual,则告诉编译器您希望它引用{{1 }},并使用该类型(或其适当的超类型)中pa的定义。

C ++的语义必须是确定性的。也就是说,作为程序员,您需要能够预测在任何给定情况下将使用f的哪个定义。如果您考虑一下,使用一种规则指出“使用f的语言来编程是非常困难的,前提是您发现B::f指向类型{ {1}},但如果不确定pa指向的内容,请使用B。编译器应如何努力找出A::f指向什么?如果将来,编译器团队中的某人想出如何做出更准确的确定,您的程序的语义是否应该神奇地改变?您会为此感到满意吗?

请注意,在您提供的两个摘要中,编译器可以实际上很可能找出pa所指向的对象的类型。因此,即使papa,优化的编译器也可以省略代码以在vtable中查找正确的方法,而直接调用正确的方法。 C ++标准中没有任何东西可以阻止这样的优化,我相信这很普遍。因此,如果将来编译器团队中的某人想出一种确定变量类型的更好方法,它将不会更改程序的语义-仍将调用相同的方法。但这可能会导致您的程序更快地调用您的方法。这样的结果更有可能使你的未来快乐。

答案 1 :(得分:1)

所有这些都解决了C ++是静态类型的语言这一事实​​。因此,编译器将<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>视为指向pa的指针,而不是指向A的指针(因为您这样声明),因此,如果您调用{ {1}}将调用B而不是pa->f(),除非A::f()被声明为虚拟。

实际上,这就是虚拟方法的重点-当您通过多态指针调用该方法时,将其分派到正确的方法。

答案 2 :(得分:0)

在所示的非常简单的示例中,是的,编译器可以轻易发现pa是指向动态类型为B的对象的指针。

但是,为了以您描述的方式使用该信息,必须做到以下几点:

  1. 在所有情况下,指针的工作规则都必须要求
  2. 该信息将不仅需要一个简单的/人为的示例来提供(它不是-将pa传递给另一个翻译单元中的函数,突然之间,编译器运行对该单元的处理并不知道动态类型是什么;三个小时前您问这个问题时,给出了一些examples
  3. 我们(作为程序员)通常会想要这种行为(我们不愿意—如果愿意,我们可以选择virtual选择加入,否则我们希望简单的行为表现为表达式定义了表达式的含义以及操作数的作用。

最后,请注意,将动态 vs 自动存储持续时间/分配方法描述为“运行时”与“编译时”并不十分准确,尽管无论如何,这些术语都会变得很笨拙构建和运行C ++程序涉及不同的抽象层。