简体   繁体   English

我明白为什么了,但是虚拟函数/ VTables如何正确地允许通过指针访问正确的函数呢?

[英]I get why, but how exactly do Virtual Functions/VTables allow the correct functions to be accessed through pointers?

I found some excellent threads on this site for the subject, and the topic of polymorphism is cleared up for me, but I'm just confused how exactly a virtual function works versus a normal function. 我在该站点上找到了该主题的出色线索,而多态性的主题也为我清除了,但是我只是对虚函数与普通函数的工作原理感到困惑。

(an example given in this thread Why do we need virtual functions in C++? ): (此线程中给出的示例为何我们需要C ++中的虚函数? ):

class Animal
{
public:
    void eat() { std::cout << "I'm eating generic food."<<endl; }
};

class Cat : public Animal
{
public:
    void eat() { std::cout << "I'm eating a rat."<<endl; }
};

void func(Animal *xyz) { xyz->eat(); }

So We have a function and a derived function that was redefined. 因此,我们有一个函数和一个重新定义的派生函数。

Cat *cat = new Cat;
Animal *animal = new Animal;


animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."
func(animal);  // Outputs: "I'm eating generic food."
func(cat);     // Outputs: "I'm eating generic food."

So we can't access the function we want without it being a virtual function. 因此,如果它不是虚拟函数,就无法访​​问所需的函数。 But why, exactly? 但是为什么呢?

If the following works: 如果以下有效:

Cat cat;
Animal animal;

animal.eat(); // Outputs: "I'm eating generic food."
cat.eat();    // Outputs: "I'm eating a rat."

Then presumably there are two different eat functions in memory already without needing a vtable. 然后大概在内存中已经有两个不同的eat函数,而无需使用vtable。

So when we make eat a virtual function, each class now gets its own vTable with its own functions. 因此,当我们制作一个虚拟函数时,每个类现在都将获得具有自己函数的vTable。 So...We are just storing the functions in another place in memory. 所以...我们只是将函数存储在内存中的另一个位置。 So what happens to a pointer between when it calls a regular function through an object and when it calls a virtual function through an object? 那么当指针通过对象调用常规函数与通过对象调用虚拟函数之间发生了什么变化?

As in what's the difference between: Animal->eat(); 就像之间的区别是:Animal-> eat(); //Calling a virtual function and Animal->eat(); //调用虚拟函数和Animal-> eat(); //Calling a regular function //调用常规函数

When we declare virtual function, TutorialsPoint says 当我们声明虚函数时,TutorialsPoint说

This time, the compiler looks at the contents of the pointer instead of it's type 这次,编译器查看指针的内容,而不是类型

Yes, but how? 是的,但是怎么? Why couldn't it do that before? 为什么以前不能做到这一点? It's presumably just stored in memory the same as a regular function. 大概它只是和常规函数一样存储在内存中。 Does it have something to do with the Vtable pointer at the beginning of an object? 它与对象开头的Vtable指针有关吗?

I'm just looking for specifics to understand why. 我只是在寻找具体细节以了解原因。 I don't mean to sound like I'm getting bogged down in something ultimately pointless. 我并不是说听起来好像陷入了最终毫无意义的事情。 Just wondering for academic reasons. 只是出于学术原因想知道。

Consider this code: 考虑以下代码:

void Function(Animal *foo)
{
    foo->eat();
}

If eat is a non-virtual member function, this just calls Animal::eat . 如果eat是非虚拟成员函数,则仅调用Animal::eat It makes no difference what foo points to. foo指向什么没有关系。

If eat is a virtual member function, this is roughly equal to *(foo->eatPtr)(); 如果eat是一个虚拟成员函数,则大致等于*(foo->eatPtr)(); . You can think of Animal , and all its derived classes, as having a member variable that points to the eat function. 您可以将Animal及其所有派生类视为具有指向eat函数的成员变量。 So if foo actually points to a Bear , then foo->eatPtr() will access Bear::eatPtr and call the eat function of the Bear class. 因此,如果foo实际上指向Bear ,则foo->eatPtr()将访问Bear::eatPtr并调用Bear类的eat函数。

Which function to call is determined at compile time for non-virtual functions. 对于非虚拟函数,在编译时确定要调用哪个函数。 So this will always call the same eat function. 因此,这将始终调用相同的eat函数。 For a virtual function, the pointer passed in is used to find the appropriate virtual function table for the particular class that foo happens to be a member of. 对于虚函数,传入的指针用于为foo恰好是其成员的特定类找到适当的虚函数表。

This extra class member variable that points to the vtable for the class is why the size of either a class instance or its pointers (depending on the implementation) will typically go up by the size of one pointer when you add the first virtual function to that class. 指向该类的vtable的这个额外的类成员变量是为什么当您向其添加第一个virtual函数时,类实例或其指针(取决于实现)的大小通常会增加一个指针的大小的原因类。

I find it best to implement it to understand it. 我发现最好实施它来理解它。

struct ifoo;
struct ifoo_vtable{
  void(*print)(ifoo const*);
};

struct ifoo{
  ifoo_vtable const* vtable;
  void print()const{ vtable->print(this); }
};

struct fooa:ifoo{
  void print_impl()const{ std::cout<<"fooa\n"; }
  fooa(){
    static const ifoo_vtable mytable={
      +[](ifoo const* self){
        static_cast<fooa const*>(self)->print_impl();
      }
    };
    vtable=&mytable;
  }
};
struct foob:ifoo{
  void print_impl()const{ std::cout<<"foob\n"; }
  foob(){
    static const ifoo_vtable mytable={
      +[](ifoo const* self){
        static_cast<foob const*>(self)->print_impl();
      }
    };
    vtable=&mytable;
  }
};

now there is zero use of virtual above. 现在virtual上面的使用为零。 But: 但:

fooa a;
foob b;
ifoo* ptr = (rand()%2)?&a;&b;
ptr->print();

will randomly call either fooa or foob's print_impl method. 将随机调用fooa或foob的print_impl方法。

The vtable is a structure of pointers to functions. vtable是指向函数的指针的结构。 Invoking a virtual methid actuall runs a little stub that looks up the method in the vtable, then runs it. 调用虚拟方法实际运行一个小桩,该桩在vtable中查找方法,然后运行它。

Code written for you in implementatiin classes' constructors fills that vtable with function pointers pointing at the overrides. 在实现类的构造函数中为您编写的代码用指向覆盖的函数指针填充该vtable。

Now there are details that are not done here -- calling conventions, destructors, devirtualization, multiple interface inheritance, dynamic casts, etc -- but the core is pretty similar to how every major C++ compiler implements virtual methods. 现在,这里有未完成的细节(调用约定,析构函数,去虚拟化,多接口继承,动态强制转换等),但是其核心与每个主要C ++编译器如何实现虚拟方法非常相似。

Under the standard this is not detailed; 根据标准,这没有详细说明。 only behaviour is. 唯一的行为就是。 But this kind of vtable comes from before C++ was a language, and this kind of vtable is what I believe the C++ language designers had in mind when they specified the behaviour of virtual functions in C++. 但是这种vtable起源于C ++是一种语言之前,而我认为C ++语言设计人员在指定C ++中的虚函数行为时要牢记这种vtable。

Note that this is far from the only way to do this. 请注意,这远非唯一的方法。 MFC message maps, objective C/small talk based messages, python, lua, and many other languages have other ways to do this with advantages and disadvantages over C++'s solution. MFC消息映射,基于目标C /基于聊天的消息,python,lua和许多其他语言具有其他实现此目的的方法,它们比C ++解决方案具有优势和劣势。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如果使用vtable实现具有虚函数的类,那么如何实现没有虚函数的类? - If classes with virtual functions are implemented with vtables, how is a class with no virtual functions implemented? 如何创建和指向函数的指针数组? - How do I create and array of pointers to functions? 如何将智能指针传递给函数? - How do I pass smart pointers into functions? 虚拟函数在哪里使用vpointer到vtables来解决方法调用,非虚拟方法在哪里存储以及如何解决? - Where virtual functions use vpointers to vtables to resolve the method call, where are non-virtual methods stored and how are they resolved? 如何使用函数指针或虚拟函数来允许其他程序员定义函数的实现? - How to use function pointers or virtual functions to allow another programmer to define a function's implementation? 函数如何读取指针? - how do functions read pointers? 通过指针调用虚函数,无论是否咨询 VM 表 - calling virtual functions through pointers with and without consulting the VM-table C++ 链接时对基类函数的未定义引用(--not-- vtables、构造函数、模板或纯虚函数) - C++ Undefined reference to base class functions at link time (--not-- vtables, constructors, templates, or pure virtual functions) 为什么我需要重新声明重载的虚函数? - Why do I need to redeclare overloaded virtual functions? 协变虚函数和智能指针 - Covariant virtual functions and smart pointers
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM