#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::fun()?
此外,请解释使用virtual
时会发生什么,这与本例不同。
当成员函数不是virtual
时,调用的函数仅由点(.
)或箭头(->
)运算符左侧的表达式类型决定。这被称为"静态类型"。
当成员函数为virtual
时,调用的函数由对象的实际最派生类型确定,该对象由点左侧的表达式命名(.
)或由箭头左侧的表达式指向(->
)。这被称为"动态类型"。
请注意,当点左侧使用的变量、成员、参数或返回类型具有普通类类型时,静态类型和动态类型始终相同。但是,如果变量、成员、参数或返回类型是对类类型的指针或引用,则静态类型和动态类型可能不同。
请参阅http://www.cs.technion.ac.il/users/yechiel/c++-faq/dyn-binding.html,我引用:
非虚拟成员函数是静态解析的。也就是说,成员函数是根据对象的指针(或引用)类型静态选择的(在编译时)。
相反,虚拟成员函数是动态解析的(在运行时)。也就是说,成员函数是根据对象的类型而不是该对象的指针/引用的类型动态选择的(在运行时)。这被称为";动态绑定"大多数编译器使用以下技术的一些变体:如果对象有一个或多个虚拟函数,编译器会在名为"的对象中放置一个隐藏指针;虚拟指针";或";v指针"这个v指针指向一个称为";虚拟表";或";v-table">
查看以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; }
以上三个基本上都是一样的(调用约定之类的小细节——寄存器或堆栈位置参数的执行方式——可能会有所不同)。
非虚拟成员函数只是使用名为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
s。
Sexand,我们有一个函数指针表(这里只包含一个),称为vtable。我们的bob
包含一个指向vtable的指针作为它的第一个条目。在它中,我们有一个指向get_x_impl
的函数指针。最后,bob
有一个get_x
方法,它通过vtable、函数指针将调用转发到get_x_impl
。
在构造过程中,派生类型可以通过get_x
的不同实现将vtable指针更改为指向不同的函数表。
然后,当ypu有一个指向bob
的指针并调用get_x
时,它将跟随更改后的vtable中的指针,并调用替换实现。
当你创建一个虚拟函数时,像上面这样的机器就是为你编写的。当您继承和重写时,用派生的替换父vtable指针的代码将被注入到派生类型的构造函数中。
所有这些基本上都实现了在C++存在之前,C语言中的人可以做的事情。他们只是隐藏了细节,并用C++为您编写了粘合代码。