【问题标题】:How functions are resolved by compiler?编译器如何解析函数?
【发布时间】:2011-01-06 17:30:51
【问题描述】:

如何判断下面的调用是在编译时绑定还是在运行时绑定?

object.member_fn;//object is either base class or derived class object
p->member_fn;//p is either base class or derived class pointer

编辑:

#include <iostream>
using namespace std;
class Base
{
       public:
          Base(){ cout<<"Constructor: Base"<<endl;}
          ~Base(){ cout<<"Destructor : Base"<<endl;}
};
class Derived: public Base
{
     //Doing a lot of jobs by extending the functionality
       public:
           Derived(){ cout<<"Constructor: Derived"<<endl;}
           ~Derived(){ cout<<"Destructor : Derived"<<endl;}
 };
void foo()
{
    Base & Var = Derived();
    Base*pVar = new Derived;
    delete pVar;
}
void main()
{
    foo();
        std::cin.get();
}


out put:
Constructor: Base
Constructor: Derived
Constructor: Base
Constructor: Derived
Destructor : Base // the Derived is not called,
                  // the PVal is of type Base* and the fn is not virtual 
                  //so compile time binding
Destructor : Derived
Destructor : Base

【问题讨论】:

    标签: c++ binding runtime polymorphism compile-time


    【解决方案1】:

    如果该方法不是虚拟的,则两个调用都将在编译时解析。如果该方法是虚拟的,那么您问题中的第一个调用 (obj.method()) 将在编译时为对象解析,但在运行时解析为引用。第二次调用 (objp-&gt;method()) 将在运行时解决。您还可以在编译时强制调用方法的非派生版本。

    struct base {
       void f();
       virtual void v();
    };
    struct derived : public base {
       void f();
       void v(); // intentionally left virtual out, it does not really matter
    };
    int main() {
       derived d;
       base & b = d;
       base * bp = &d;
    
       // compile time:
       d.f();   // derived::f
       d.v();   // derived::v
       b.f();   // base::f   -- non-virtual
       bp->f(); // base::f   -- non-virtual
    
       // runtime:
       b.v();   // derived::v
       bp->v(); // derived::v
    
       // compile time (user forced):
       b.base::v();   // base::v
       bp->base::v(); // base::v
    }
    

    【讨论】:

    • +1,但我无法隐含地理解第一次调用是什么意思,可以在下面的代码中进行第一组调用,第二组调用和第三组调用类似。
    • 你说的事情在运行时解决可能实际上由编译器优化以在编译时执行。如果它有足够的信息和足够的智慧向自己证明该对象必须属于某个特定的类,那么虚拟调用就可以成为非虚拟的。它们“原则上”在运行时解决。
    • 通过这个简单的例子,编译器实际上可以执行这些优化,但在大多数现实世界的场景中(引用或指针被传递给函数或从另一个函数作为返回值接收)编译器实际上不能在那里做很多事情(除非再次,该函数在编译单元中可见--inlined ...)。最后,无论有多少不同的场景可以优化,编译器在现实世界的应用程序中执行这些优化的实际机会对于纯 OO 风格来说是很小的,而对于元编程来说就更少了。
    • 同意,只是因为问题是“它是如何确定...”,我认为答案应该提到它有时取决于编译器。我没有理由想知道对虚拟函数的调用是否实际上是固定的,就好像它是非虚拟的一样,您也不想知道它是依赖于实现的行为。
    【解决方案2】:

    在第一种情况下,object 的类型在编译时是已知的(假设它不是引用)。所以这将是一个静态绑定。

    在第二种情况下,将使用动态绑定,前提是函数是virtual。否则,静态绑定。

    【讨论】:

    • 引用是编译时的概念,指针是运行时的概念——它们有不同的用途:引用意味着允许通过引用调用语义,而不必求助于指针。
    • @Beginner:是的,引用的行为类似于指针,因为它在内部就是这样。
    • 但事实并非如此,当派生对象被分配给 Base 的引用时,它的行为就像对象而不是指针,该类不是多态的
    • -1。尽管这个答案在技术上是正确的,但它非常具有误导性。对这两种情况所做的假设与代码无关。您可以颠倒它们并为每个得出相反的结论。
    • @Potatoswatter 我认为他的回答是正确的,代码是稍后添加的。
    【解决方案3】:

    我认为需要明确的是,第一种情况下的构造函数和析构函数调用是针对未命名的派生对象进行的,它们不是 Var 的构造函数或析构函数。 Var 只是一个引用类型,不需要任何特殊处理。由于编译器知道这个未命名的派生对象的类型,它会正确地将这些对象静态绑定到派生类。

    同样,构造函数在第二种情况下是静态绑定的,因为 new 后面的类型是派生的。但是,当您对 base 类型的指针调用 delete 时,编译器会调用 Base 的析构函数(同样使用静态绑定)。

    如果您将基的析构函数声明为虚拟,那么这个最终绑定 - 删除发生时调用的析构函数将是动态的,您将获得与第一种情况相同的输出。

    【讨论】:

      【解决方案4】:

      动态绑定仅在指针/引用和调用的函数是虚拟的情况下使用,

      object.member_fn;//if object is not reference so static binding
      object.member_fn;//if object is reference and member_fn is virtual dynamic binding
      p->member_fn;//p is pointer and member_fn is virtual dynamic binding
      

      虽然有一种情况是引用将自身绑定到一个临时对象,但会调用正确的析构函数。

      #include <iostream>
      using namespace std;
      //virtual behavior with out the type being polymorphic
      class Base
      {
      public: Base(){}
          ~Base(){}
      };
      class Derived:public Base
      {
      public:Derived(){}
             ~Derived(){
                 std::cout<<"Destructor is not virtual, but it is called";
             }
      };
      void foo(){
          Base & bound_to_temporary = Derived();
      }
      int main(){
          foo();
          cin.get(); 
      }
      

      输出: 析构函数不是虚拟的,但它是被调用的。

      【讨论】:

      • ... 如果编译器无法预测动态类型。例如。在std::string const&amp; foo = "FOO"; 之后,编译器可以可靠地预测foo 的动态类型,即使它是一个引用。
      猜你喜欢
      • 2013-09-20
      • 1970-01-01
      • 2016-07-26
      • 2020-09-04
      • 1970-01-01
      • 2018-09-09
      • 2017-12-06
      • 1970-01-01
      • 2014-06-04
      相关资源
      最近更新 更多