调用虚拟和非虚拟成员函数之间的区别是什么?

时间:2017-10-31 00:11:46

标签: c++ inheritance

#include<iostream>
using namespace std;
int main(){
    class c1{

        public:
         int func(){
            cout<<"in the  c1";
         }

    };

    class c2:public c1{

        public:
        int func(){
            cout<<" in c2";
        }
    };


c1* a;
c2 b;

a=&b;
a->func();

}

我知道我应该使用虚函数来获得所需的结果,但我想知道上面的代码是怎么回事。为什么调用c1 :: func()而不是c2 :: FUNC()?

另请注意使用virtual时与本案不同的情况。

3 个答案:

答案 0 :(得分:4)

当成员函数不是virtual时,调用的函数仅由点(.)或箭头(->)运算符左侧的表达式类型决定。这称为“静态类型”。

当成员函数为virtual时,调用的函数由点的左侧表达式(.)指定的或由左侧表达式指向的对象的实际派生类型确定箭头(->)。这被称为“动态类型”。

请注意,当用于点左侧的变量,成员,参数或返回类型具有普通类类型时,静态类型和动态类型始终相同。但是,如果变量,成员,参数或返回类型是类类型的指针或引用,则静态类型和动态类型可以不同。

答案 1 :(得分:1)

http://www.cs.technion.ac.il/users/yechiel/c++-faq/dyn-binding.html,我引用:

  

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

     

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

答案 2 :(得分:0)

查看以C风格实现的虚拟和非虚函数可能会有所帮助。

struct bob {
  int x;
  static int get_x_1( bob* self ){ return self->x; }
  int get_x_2() { return this->x; }
};
inline int get_x_3( bob* self ){ return self->x; }

以上所有3个基本上是相同的东西(调用约定的小细节 - 寄存器或堆栈位置参数继续 - 可能不同)。

非虚拟成员函数只是采用名为this的半秘密指针的函数。半秘密,因为它就在方法名称的左侧。

这一点很重要。非虚拟调用只是花哨的函数调用。该类的实例不存储指向其方法的指针,或类似的东西。它们只是单调函数调用的语法糖。

现在虚拟成员函数不同。

以下是我们如何实现这一点:

class bob{
public:
  virtual int get_x(){ return x; }
  int x;
};

不使用virtual

struct bob;
using get_x=int(bob*);
struct bob_vtable {
  get_x* get_x=0;
};
inline int get_x_impl( bob* self );
bob_vtable* get_bob_vtable(){
  static bob_vtable vtable{ get_x_impl };
  return &vtable;
}
struct bob {
  bob_vtable* vtable=0;
  int x;
  int get_x(){ return this->vtable->get_x(this); }
  bob(): vtable(get_bob_vtable()) {}
};
inline int get_x_impl( bob* self ){ return self->x; }

很多事情正在发生。

首先我们有get_x_impl,这与上面的非虚拟get_x非常相似。

Sexond,我们有一个函数指针表(这里只有一个)称为vtable。我们的bob包含指向vtable的指针作为其第一个条目。在其中我们有一个指向我们get_x_impl的函数指针。最后,bob有一个get_x方法,可以通过vtable将调用通过函数指针转发到get_x_impl

在构造过程中,派生类型可以更改vtable指针以指向不同的函数表,并使用get_x的不同实现。

然后当ypu有一个指向bob的指针并调用get_x时,它将跟随更改的vtable中的指针并调用替换实现。

创建虚拟功能时,会为您编写上述机器。当你继承和覆盖时,用派生替换父vtable指针的代码被注入到派生类型的构造函数中。

所有这些基本上都是在C ++存在之前实现C中人们可以做的事情。他们只是隐藏了细节,并用C ++编写了胶水代码。