简体   繁体   中英

pointer to access member function through virtual pointer

I came across articles where in they explain about vptr and vtable. I know that the first pointer in an object in case of a class with virtual functions stored, is a vptr to vtable and vtable's array entries are pointers to the function in the same sequence as they occur in class ( which I have verified with my test program). But I am trying to understand what syntax must compiler put in order to call the appropriate function.

Example:

class Base
 {
   virtual void func1() 
   { 
       cout << "Called me" << endl; 
   }
};
int main()
{
  Base obj;
  Base *ptr;
  ptr=&obj;

// void* is not needed. func1 can be accessed directly with obj or ptr using vptr/vtable
  void* ptrVoid=ptr; 

// I can call the first virtual function in the following way:
  void (*firstfunc)()=(void (*)(void))(*(int*)*(int*)ptrVoid); 
  firstfunc();
}

Questions:

1. But what I am really trying to understand is how compiler replaces the call to ptr->func1() with vptr ? If I were to simulate the call then what should I do? should I overload the -> operator. But even that would not help as I would not know what really the name func1 is. Even if they say that compiler accesses the vtable through vptr, still how does it know that the entry of func1 is the first array adn entry of func2 is the second element in the array? There must be some mapping for the names of function to the elements of array.

2. How can I simulate it. Can you provide the actual syntax that compiler uses to call function func1 (how does it replace ptr->func1() )?

Don't think of a vtable as an array. It's only an array if you strip it of everything C++ knows about it other than the size of its members. Instead, think of it as a second struct whose members are all pointers to functions.

Suppose I have a class like this:

struct Foo {
    virtual void bar();
    virtual int baz(int qux);
    int quz;
}

int callSomeFun(Foo* foo) {
    foo->bar();
    return foo->baz(2);
}

Breaking it down 1 step:

class Foo;
// adding Foo* parameter to simulate the this pointer, which
// in the above would be a pointer to foo.
struct FooVtable {
    void (*bar)(Foo* foo);
    int (*baz)(Foo* foo, int qux);
}
struct Foo {
    FooVtable* vptr;
    int quz;
}

int callSomeFun(Foo* foo) {
    foo->vptr->bar(foo);
    return foo->vptr->baz(foo, 2);
}

I hope that's what you're looking for.

The backgroud:

  1. After compilation (without debug info) binaries of C/C++ have no names, and names aren't required to runtime work, its only machine code

  2. You can think about vptr like clasic C function pointer, in sense that type, argument list etc is known.

  3. It isn't important on which positions are placed func1, func2 etc, only required is order was always the same (so all parts of multi file C++ must be compiled in the same way, compiler settings etc). Lets imagine, position is in declaration order, FIRST parent class, then newly declared in override BUT reimplemented virtuals are at lower positions, like from parent.

Its only image. Implementation must correctly fire overrides classApionter->methodReimplementedInB()

  1. Usually C++ compiler has/had (my knowledge is from years 16/32b migration) 2-4 option to optimalize vtables against speed/size etc. Classic C sizeof() was quite well to understand (size of data plus ev. alignment), in C++ sizeof is bigger, but can guarantee if it is 2,4,8 bytes.

4 Few conversion tool can convert "object" files ie from MS format to Borland etc, but usually/only classic C was possible/safe, because of unknown machine code implementations of vtable.

  1. Hard to touch vtable from high level code, fire analysers for intermediate files (.obj, . etc)

EDIT: story about runtime is different than about compilation. My answer is about compiled code & runtime

EDIT2: quasi assembler code (from my head)

load ax, 2
call vt[ax]

vt:
0x123456
0x126785  // virlual parent func1()

derrived:

vt:
0x123456
0x126999 // overriden finc1()
0x456788 // new method

EDIT3: BTW I can't totally agree that C++ has always better speed JVM/.NET because "these are interpreted". C++ has part of "intepretation", and interpreted part is groving: real component/GUI frameworks have interpreted connections between too (map for example). Out of our discussion: what memory model is better, with C++ delete or GC?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM