【问题标题】:How virtual and non-virtual functions are called如何调用虚函数和非虚函数
【发布时间】:2015-09-17 05:38:45
【问题描述】:
using namespace std;

class Foo
{
    public:
        virtual void foo();
        void foo2();
};
class Bar : public Foo
{
    public:
        void foo();
        void foo2();
};
int main()
{
    Foo* f = new Foo;
    f->foo();         **//1**
    f->foo2();       **//2**
    return 0;
}

编译器如何知道,1) 本质上是动态的,2) 是静态的。 两者是如何在内部调用的。

【问题讨论】:

  • 如果我误解了你的问题的意图,它更像是“编译器如何解析为虚函数调用哪个函数”然后read here
  • 你应该看看virtual functions上的isocpp.org FAQ
  • 我故意编写代码以便澄清疑问,在 1) 编译器将决定使用虚拟表,它必须调用 Foo::foo 因为指针是基类 & 在 2) 编译器静态决定调用 Foo::foo2。那么编译器在阅读语句后如何区分这两种情况。

标签: c++ virtual-functions


【解决方案1】:

Collected from here . . .

非虚拟成员函数是静态解析的。也就是说,成员函数是根据指向对象的指针(或引用)的类型(在编译时)静态选择的。

相比之下,虚拟成员函数是动态解析的(在运行时)。也就是说,成员函数是根据对象的类型(在运行时)动态选择的,而不是基于该对象的指针/引用的类型。这称为“动态绑定”。大多数编译器使用以下技术的一些变体:如果对象具有一个或多个虚拟函数,编译器会在对象中放置一个隐藏指针,称为“虚拟指针”或“v指针”。这个 v-pointer 指向一个名为“virtual-table”或“v-table”的全局表。

纯虚函数是必须在派生类中重写且无需定义的函数。使用奇怪的 =0 语法将虚函数声明为“纯”。例如:

class Base {
public:
    void f1();      // not virtual
    virtual void f2();  // virtual, not pure
    virtual void f3() = 0;  // pure virtual
};
Base b; // error: pure virtual f3 not overridden

这里,Base 是一个抽象类(因为它有一个纯虚函数),所以不能直接创建 Base 类的对象:Base(明确地)意味着是一个基类。例如:

class Derived : public Base {
    // no f1: fine
    // no f2: fine, we inherit Base::f2
    void f3();
};
Derived d;  // ok: Derived::f3 overrides Base::f3

虚拟或非虚拟围栏示例

#include <iostream>
using namespace std;

class Base {
    public:
          virtual void NameOf();   // Virtual function.
          void InvokingClass();   //  Nonvirtual function.
          };

// Implement the two functions.
void Base::NameOf() {
    cout << "Base::NameOf\n";
   }

void Base::InvokingClass() {
    cout << "Invoked by Base\n";
   }

class Derived : public Base {
    public:
          void NameOf();   // *Virtual function*.
          void InvokingClass();   // *Nonvirtual function.*
  };

// Implement the two functions.
void Derived::NameOf() {
    cout << "Derived::NameOf\n";
 }

void Derived::InvokingClass() {
   cout << "Invoked by Derived\n";
 }

主要

int main() {
   // Declare an object of type Derived.
      Derived aDerived;

   // Declare two pointers, one of type Derived * and the other
   //  of type Base *, and initialize them to point to aDerived.
      Derived *pDerived = &aDerived;
      Base    *pBase    = &aDerived;

  // Call the functions.
     pBase->NameOf();           // Call virtual function.
     pBase->InvokingClass();    // Call nonvirtual function.
     pDerived->NameOf();        // Call virtual function.
     pDerived->InvokingClass(); // Call nonvirtual function.
}

【讨论】:

    【解决方案2】:

    在您的示例中,foo()foo2() 都来自 Foo 类。

    int main()
    {
        Foo* f = new Foo;
        f->foo();                        // Foo::foo
        f->foo2();                       // Foo::foo2
        return 0;
    }
    

    为了说明virtual 的行为,您需要创建派生类的实例

    int main()
    {
        Foo* b = new Bar;
        b->foo();                        // Bar::foo
        b->foo2();                       // Foo::foo2
        static_cast<Bar*>(b)->foo2();    // Bar::foo2
        return 0;
    }
    

    注意后一种情况,因为b实际上是一个Bar,它调用了被覆盖的虚方法foo。但是由于foo2 没有声明为virtual 并且bFoo,它将调用Foo::foo2。但是,如果我们将f 转换为Bar,它将调用Bar::foo2

    【讨论】:

      【解决方案3】:

      virtual 关键字告诉编译器进行动态绑定。

      要查看动态绑定实例化 Foo 指针与 Bar 对象,请参阅下面的代码。

      #include <iostream>
      #include <string>
      #include <algorithm>
      
      using namespace std;
      
      class Foo
      {
          public:
              virtual void foo(){std::cout<<"Foo foo"<<std::endl;};
              void foo2(){std::cout<<"Foo foo2"<<std::endl;};
      };
      class Bar : public Foo
      {
          public:
              void foo(){std::cout<<"Bar foo"<<std::endl;};
              void foo2(){std::cout<<"Bar foo2"<<std::endl;};
      };
      int main()
      {
          Foo* f = new Bar;
          f->foo();
          f->foo2();
          return 0;
      }
      

      【讨论】:

      • 我故意编写代码以便澄清疑问,在 1) 编译器将决定使用虚拟表,它必须调用 Foo::foo 因为指针是基类 & 在 2) 编译器静态决定调用 Foo::foo2。那么编译器在阅读语句后如何区分这两种情况。
      【解决方案4】:

      一个声明或继承虚函数的类有一个叫做vtable的东西,当你调用一个虚函数时,它用于查找调用哪个函数。实际上,该表包含指向类中所有虚函数的指针,如下所示(伪代码 - 这可能会或可能不会编译):

      class Foo {
          void foo_impl(){std::cout<<"Foo foo"<<std::endl;}
          struct {
              void (*foo_ptr)();
          } vtable;
          public:
              Foo(){vtable.foo_ptr = &Foo::foo_impl;}
              void foo(){vtable.foo_ptr();}
              void foo2(){std::cout<<"Foo foo2"<<std::endl;}
      };
      class Bar : public Foo {
          void foo_impl(){std::cout<<"Bar foo"<<std::endl;}
          public:
              Bar(){vtable.foo_ptr = &Bar::foo_impl;}
              void foo2(){std::cout<<"Bar foo2"<<std::endl;}
      };
      

      因此,当你调用一个虚函数时,首先在vtable中查找地址,所以如果你分配一个Bar bar; Foo&amp; foo = bar;,那么foo.foo()调用Bar的版本foo() 而不是 Foo 的版本。

      【讨论】:

        猜你喜欢
        • 2012-02-06
        • 1970-01-01
        • 2015-04-20
        • 2021-07-07
        • 2012-01-28
        • 1970-01-01
        • 2020-07-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多