Two questions related to virtual functions

前端 未结 4 1175
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-20 02:28

I was reading this articles , was at this heading Inheritance of Base-class vPtrs , but couldn\'t understand what did he mean in this para :

"However, due to mul

相关标签:
4条回答
  • 2020-12-20 02:44

    With regards to your first question, I don't really follow what the quoted passage is getting at; it actually sounds like the author doesn't really understand how vtables work (or hasn't thought about it in detail). It's important to realize that when we speak of “merging” the base and the derived classes' vtables, we are talking about making the base class vtable a prefix of the derived class table; the base class vtable must start at the start of the derived class vtable for this to work; the offset of the vptr in both the base and the derived must be identical (almost always 0, in practice), and the base class must be placed at the very beginning of the derived. And of course, it's only possible to fulfil those conditions for one base class. (Most compilers will use the first non-virtual base appearing in a left to right scan of the code.)

    With regards to the expression, it's completely undefined behavior, and will not work with some compilers. Or may or may not work, depending on the level of optimization. And the void* in it is being used as a placeholder for any number of pointer types (including, probably, pointer to function types). If we take the inner-most part, we're saying that &a is a pointer to (1 or more) void*. This pointer is then dereferenced ((X)[0] is the same as *(X), so (((void**)(&a))[0]) is the same as *(void**)(&a). (The [0] notation suggests that there might be more values behind this one; i.e. that [1], etc. might also be valid. That's not the case here.) This results in a void*, which is then cast to a void** and once again dereferenced, this time really using an index (since it is hopefully into an array); the result of this dereferencing is converted to a foo (a pointer to a function), which is then dereferenced and the function is called without any arguments.

    None of this will actually work. It makes a number of suppositions which are not always, or in some cases even generally, true:

    • The offset of the vptr in an object is 0. (This one is generally true.)
    • The vptr itself has the same size as a void*. (This is almost always true, and required by Posix.)
    • The vtable itself is an array of pointers to functions, and that pointers to functions have the same size as a void*. And while it's true that pointers to functions do often have the same size as void* (again, Posix requires it, and it's also true under Windows), it's hard to imagine an implementation which would work if the vtable were just an array of pointers to functions.
    • That the function called doesn't actually use the this pointer: this is only true in exceptional cases.

    He apparently is using VC++ (based on the __thiscall, which is a Microsoft'ism), and I've only analysed the layout of Sun CC, which is definitely different. (And Sun CC and g++ are also very different—for that matter, Sun CC 3.1, Sun CC 4.0 and Sun CC 5.0 are all different.)

    Unless you're actually writing a compiler, I'd ignore all of this. And I'd certainly ignore the expression you quote.

    0 讨论(0)
  • 2020-12-20 02:52

    If Derived inherits from Base, its vtable will extend Base's. Now, if it also inherits Base2, its vtable will not include Base2's - Base2's part will retain its vtable (updated with Derived virtual functions, if they override Base2's).

      Base members      Base2 members    Derived members
    +--+------------+----+------------+------------------+
     |                |
     V                V
    Derived + Base   Base2 vtable  
    vtable
    

    To make the second question easier to understand, and since I enjoy drawing with fixed width font... here's the memory layout of a. For a full explanation about that expression, see James Kanze's answer.

            +---+----------+
    a:      | | |    A     |
            +-+-+----------+
              |
              V
    vtable: +---+
            | --+--> f2()
            +---+
            | --+--> f3()
            +---+
    

    HTH...

    0 讨论(0)
  • 2020-12-20 02:58

    Virtual function calls are commonly implemented using a virtual method table. Compilers typically create one such table for each class in your program, and give each object a pointer to the virtual table that corresponds to its class when the object is instantiated (this pointer is referred to as vptr). This way, the object "knows" exactly which function to call when you call a virtual method regardless of its static type.

    As I mentioned above, normally each class gets its own virtual method table. The paragraph you cite says that if a class derives from e.g. 5 base classes, each of which also derives from 5 other classes, then the virtual table for this class should end up being a merged version of all 25 virtual tables of the base classes. This would be kind of wasteful, so the compiler might decide to only merge the 5 virtual tables from the "immediate" base classes into the derived class's virtual table, and keep the vptrs to the other 20 virtual tables stored as a hidden member inside the class (which would now have 21 vptrs total).

    The benefit in doing this is that you don't need to reserve memory repeating the same information every time a class with a virtual table is derived from. The downside is that you complicate the implementation (e.g. when calling a virtual method, the compiler now has to somehow figure out which one of the vptrs points to the table that tells it which method to call).

    Regarding the second question, I 'm not sure what exactly you are asking. This code assumes that the vptr is the very first item in the memory layout of object of that class (which is practically true often, but a horrible hack since nowhere does it say that virtual methods are even implemented using a virtual table; there might not even be a vptr), fetches the second item from the table (which is a pointer to a member function of the class) and runs it.

    Expect fireworks if even the slightest thing goes wrong (e.g.: there was no vptr, the vptr's structure is not what the person who wrote the code expected, a later version of the compiler decides to change how it stores the virtual table, the method pointed to has a different signature, etc etc etc).

    Update (addressing the comments):

    Let's say we have

    class Child : Mom, Dad {};
    
    class Mom : GrandMom1, GrandDad1 {};
    
    class Dad : GrandMom2, GrandDad2 {};
    

    In this case, Mom and Dad are the immediate base classes ("first-born", but the term is misleading).

    0 讨论(0)
  • 2020-12-20 02:59

    for question 1: i think the paragraph is just saying that the vtables aren't actually merged into one and stored in the memory allocation of the class that is multiply deriving; but that the vtables of the base classes beyond the first base classes are used by reference.

    in other words; if you have Rose which derives from Flower which derives from Plant then Rose only incorporates Flower's vtable directly, but uses of Plant's vtable are done by calling them from Plant's vtable. )

    for question 2: i am not so great at doing these in my head, i would have to just start breaking it down into manageable chunks to understand.

    first i would tabify it like so:

    (
      *(foo)
      (
        (void**)
        (
          ((void**)(&a))[0]
        )
      )
      [1]
    )
    (); 
    

    then,

    Step 1:

    ((void**)(&a))[0]

    we know (X)[0] = *X

    let X = (void**)&a

    X[0] = ((void**)&a)[0] = (void*)a

    now replace:

    (
      *(foo)
      (
        (void**)
        (
          (void*)(a)
        )
      )
      [1]
    )
    (); 
    

    Step 2:

    (void**)((void*)(a)) = (void**)(void*)a = (void**)a

    (
      *(foo)
      (
        (void**)a
      )
      [1]
    )
    (); 
    

    Step 3:

    so it looks like we are left with a function pointer

    (foo)((void**)a)[1] = (foo)((void*)(a+1))

    or, what looks like a void* to function at position (a+1), of type foo...

    i think that's at least close to right :) function pointers always give me problems. ;)

    0 讨论(0)
提交回复
热议问题