C++ multiple inheritance and vtables

我们两清 提交于 2020-01-22 12:37:49

问题


So going back to basics, I'm trying to wrap my head around vtables and whatnot. In the following example, if I were to, say, pass a B* to some function, how does that function know to call the methods in the vtable of the C object instead of the methods from the vtable of A? Are there two, separate VTables that are passed to that object? Are interface pointers really just vtables (since interfaces, IIRC, cannot contain property declarations)?

What I'm trying to say is, up until I actually tried this code, I was under the assumption you couldn't inherit off of more than one interface/class at a time (and that all of the interfaces had to be linear, so to speak) so that the vtable built onto itself.

If my idea of how vtables worked was correct (which I now know it is not), then passing a B* and called B::OutB() would have called A:OutA() instead (which is obviously not the case).

Can someone shed some light?

// Includes
#include <windows.h>
#include <iostream>

interface A
{
public:
    virtual void OutA() = 0;
};

interface B
{
public:
    virtual void OutB() = 0;
};

class C : public A, public B
{
public:
    void OutA();
    void OutB();
};

void C::OutA()
{
    printf("Out A\n");
}

void C::OutB()
{
    printf("Out B\n");
}

int main()
{
    C obj;
    obj.OutA();
    obj.OutB();

    A* ap = (A*)&obj;
    B* bp = (B*)&obj;

    ap->OutA();
    bp->OutB();

    system("pause");

    // Return
    return 0;
}

outputs (as expected):

Out A
Out B
Out A
Out B

回答1:


I have no idea what an interface is, because:

  • interface is not a C++ keyword;
  • there is no concept of an "interface" in C++ semantics;
  • different C++ idioms or patterns can use the word interface for different specific purposes;
  • other languages use "interface" to describe completely different entities (in Java it's like sort of special limited base class, in O'Caml it's used where in C++ you might use a template concept).

But if you were writing C++ and A and B were classes, then C would contain two subobjects: A and B, and each of these subobject would have its own vtable pointer.

When compiling C++ to C, we could have:

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

const int debug = 0;

void __pure_virtual_called() {
    fputs ("pure virtual function called\n", stderr);
    abort();
}

/* Translation of:

class A
{
public:
    virtual void OutA() = 0;
};
*/

struct A;

typedef struct  {
    void (*ptr__OutA) (struct A *__this);
} vtable__A;

typedef struct A {
    vtable__A *__vptr;
} A;

/* translation A::OutA() 
 * pure virtual function */
void A__OutA (A *__this) {
     __pure_virtual_called();
}

vtable__A vtable__A__A = { .ptr__OutA = A__OutA };

void A__constructor (A *__this) {
    if (debug)
        printf ("A__constructor %p\n", (void*)__this);

    /* dynamic type is initialised to A */
    __this->__vptr = &vtable__A__A;
}

/* Translation of:

class B
{
public:
    virtual void OutB() = 0;
};

*/

struct B;

typedef struct {
    void (*ptr__OutB)(struct B *__this);
} vtable__B;

typedef struct B {
    vtable__B *__vptr;
} B;

/* translation B::OutB() 
 * pure virtual function */
void B__OutB (B *__this) {
     __pure_virtual_called();
}

vtable__B vtable__B__B = { .ptr__OutB = B__OutB };

void B__constructor (B *__this) {
    if (debug)
        printf ("B__constructor %p\n", (void*)__this);

    /* dynamic type is initialised to B */
    __this->__vptr = &vtable__B__B;
}
/* Translation of:

class C : public A, public B
{
public:
    void OutA(); // overrides A::OutA()
    void OutB(); // overrides B::OutB()
    // note :
    // no new virtual function
};

*/

/* no new virtual function 
 * so no specific vtable type! */

typedef struct {
/* no additional vptr, we already have 2! */
    A base__A;
    B base__B;
} C;

/******* upcasts 
 * translation of 
 * static_cast<C*> (p) 
 */

/* translation of 
 * A *p;
 * static_cast<C*> (p);
 */
C *static_cast__A__C (A *__ptr) {
    /* 
     * base__A is first member of C
     * so offsetof(C, base__A) == 0
     * can skip the pointer adjustment
     */ 
    return (C*)__ptr;
}

/* translation of 
 * B *p;
 * static_cast<C*> (p);
 */
C *static_cast__B__C (B *__ptr) {
    /* locate enclosing C object: 
     * __base__B is not first member
     * need to adjust pointer
     */
    return (C*)((char*)__ptr - offsetof(C, base__B));
}

/* translation of virtual functions of C 
 * overriding function declarations from A
 */

/* translation of C::OutA() */

/* C::OutA() called from C */
void C__OutA (C *__this) {
    printf("Out A this=%p\n", (void*)__this);
}

/* C::OutA() called from A */
void C__A__OutA (A *__this) {
    if (debug)
            printf ("C__A__OutA %p\n", (void*)__this);
    C__OutA (static_cast__A__C (__this));
}

vtable__A vtable__A__C = { .ptr__OutA = C__A__OutA };

/* translation of virtual functions of C 
 * overriding function declarations from B
 */

/* translation of C::OutB() */

/* C::OutB() called from C */
void C__OutB (C *__this) {
    printf("Out B this=%p\n", (void*)__this);
}

/* C::OutB() called from B */
void C__B__OutB (B *__this) {
    if (debug)
            printf ("C__B__OutB %p\n", (void*)__this);
    C__OutB (static_cast__B__C (__this));
}

vtable__B vtable__B__C = { .ptr__OutB = C__B__OutB };

void C__constructor (C *__this) {
    if (debug)
        printf ("C__constructor %p\n", (void*)__this);
    /* construct subobjects */
    A__constructor (&__this->base__A);
    B__constructor (&__this->base__B);

    /* adjust dynamic type of this to C */
    __this->base__A.__vptr = &vtable__A__C;
    __this->base__B.__vptr = &vtable__B__C;
}

/* calls to C virtual functions with a C* 
 */

/* translation of 
 * C *p;
 * p->OutA();
 *
 * is
 * ((A*)p)->OutA();
 *
 * because C::OutA() is overrides A::OutA()
 */
void dyn__C__OutA (C *__this) {
    A *base_ptr__A = &__this->base__A;
    base_ptr__A->__vptr->ptr__OutA (base_ptr__A);
}

/* translation of 

int main()
{
    C obj;
    obj.OutA();
    obj.OutB();

    A *ap = &obj;
    B *bp = &obj;
    C *cp = &obj;

    ap->OutA();
    bp->OutB();
    cp->OutA();

    // Return
    return 0;
}

 *
 */

int main () {
    /* translation of:
    C obj; 
    */
    C obj;
    C__constructor (&obj);

    /* translation of:
    obj.OutA();
    obj.OutB();
     * obj is a locally declared object
     * so dynamic type of obj is known as C
     * can make direct call to C::OutA(), C::OutB()
     */
    C__OutA (&obj);
    C__OutB (&obj);

    /* dumb (zero optimisation) translation of:
    A *ap = &obj;
    B *bp = &obj;
    C *cp = &obj;
    */
    A *ap = &obj.base__A;
    B *bp = &obj.base__B;
    C *cp = &obj;

    /* translation of:
    ap->OutA();
    bp->OutB();
    cp->OutA();

    * dumb compiler = no optimisation
    * so dynamic type of *ap, *bp, *cp is unknown
    * so make "virtual" calls using vtable
    */
    ap->__vptr->ptr__OutA(ap);
    bp->__vptr->ptr__OutB(bp);
    dyn__C__OutA (cp);

    /* note: obj lifetime ends now
     * C has a trivial destructor 
     * so no destructor call needed
     */

    return 0;
}

See http://ideone.com/TioyX

Output:

Out A this=0xbfeee2ec
Out B this=0xbfeee2ec
Out A this=0xbfeee2ec
Out B this=0xbfeee2ec
Out A this=0xbfeee2ec



回答2:


With multiple inheritance, the object gets built in parts, each part corresponding to one of the base classes. This includes the vtable pointers. This is necessary because the code interacting with a pointer or reference won't know if it's working with the base class or the derived one, so they must be laid out identically.

One surprising result is that when you cast a pointer to one of the base classes, its address may change! The compiler generates some code behind the scenes to adjust the pointer to the proper piece of the object.

C obj;
A* ap = (A*)&obj;
B* bp = (B*)&obj;
bool same = ((void*)ap) == ((void*)bp);  // false!


来源:https://stackoverflow.com/questions/11600670/c-multiple-inheritance-and-vtables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!