C ++多重继承和vtables

问题描述 投票:4回答:2

因此,请回到基础知识,我试图把头放在vtables之类的东西上。在下面的示例中,如果我想将B*传递给某个函数,该函数如何知道调用C对象的vtable中的方法而不是A vtable中的方法?是否有两个单独的VTable传递给该对象?接口指针真的只是vtables(因为接口,IIRC不能包含属性声明)吗?

我想说的是,直到我实际尝试这段代码之前,我一直以为您不能一次继承多个接口/类(并且所有接口必须是可以说是线性的),以便将vtable建立在其自身上。

如果我对vtables是如何工作的想法是正确的(我现在不知道),那么传递B*并调用B::OutB()会调用A:OutA()(显然不是这种情况) )。

有人可以照亮吗?

// 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;
}

输出(如预期):

Out A
Out B
Out A
Out B
c++ inheritance interface multiple-inheritance
2个回答
10
投票

我不知道interface是什么,因为:

  • [interface不是一个C ++关键字;
  • C ++语义中没有“接口”的概念;
  • 不同的C ++习惯用法或模式可以将word接口用于不同的特定目的;
  • [其他语言使用“接口”来描述完全不同的实体(在Java中,这就像是一种特殊的有限基类,在O'Caml中,它用在C ++中可能使用模板概念的地方)。

但是如果您正在编写C ++并且AB是类,则C将包含两个子对象:AB,并且这些子对象中的每个都有其自己的vtable指针。

将C ++编译为C时,我们可以有:

#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;
}

请参见http://ideone.com/TioyX

输出:

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

3
投票

具有多重继承,对象将分为多个部分构建,每个部分对应于一个基类。这包括vtable指针。这是必要的,因为与指针或引用交互的代码将不知道它是否与基类或派生类一起使用,因此必须将它们进行相同的布局。

一个令人惊讶的结果是,当您将指针转换为基类之一时,其地址可能会改变!编译器会在后台生成一些代码,以将指针调整为对象的适当部分。

C obj;
A* ap = (A*)&obj;
B* bp = (B*)&obj;
bool same = ((void*)ap) == ((void*)bp);  // false!
© www.soinside.com 2019 - 2024. All rights reserved.