można sobie wyobrazić ten kod:
struct A {
void f() {}
int int_in_b1;
};
int main() {
A a;
a.f();
return 0;
}
przekształca się w coś w rodzaju:
struct A {
int int_in_b1;
};
void A__f(A* const this) {}
int main() {
A a;
A__f(&a);
return 0;
}
Wywołanie f jest prosty, ponieważ nie jest wirtualny. (Czasami w przypadku połączeń wirtualnych można uniknąć wirtualnej wysyłki, jeśli znany jest typ dynamiczny obiektu, tak jak jest tutaj.)
A dłuższą przykład, że będzie albo daje wyobrażenie o tym, jak wirtualne funkcje działają lub strasznie mylić:
struct B {
virtual void foo() { puts(__func__); }
};
struct D : B {
virtual void foo() { puts(__func__); }
};
int main() {
B* a[] = { new B(), new D() };
a[0]->foo();
a[1]->foo();
return 0;
}
staje się czymś tak:
void B_foo(void) { puts(__func__); }
void D_foo(void) { puts(__func__); }
struct B_VT {
void (*foo)(void);
}
B_vtable = { B_foo },
D_vtable = { D_foo };
typedef struct B {
struct B_VT* vt;
} B;
B* new_B(void) {
B* p = malloc(sizeof(B));
p->vt = &B_vtable;
return p;
}
typedef struct D {
struct B_VT* vt;
} D;
D* new_D(void) {
D* p = malloc(sizeof(D));
p->vt = &D_vtable;
return p;
}
int main() {
B* a[] = {new_B(), new_D()};
a[0]->vt->foo();
a[1]->vt->foo();
return 0;
}
Każdy obiekt ma tylko jeden wskaźnik vtable, a do klasy można dodać wiele metod wirtualnych bez wpływu na rozmiar obiektu. (Vtable rośnie, ale jest przechowywany tylko raz w klasie i nie ma znacznego wzrostu narzutów). Zauważ, że uprościłem wiele szczegółów w tym przykładzie, ale to does work: destruktory nie są adresowane (co powinno dodatkowo być tutaj wirtualne), to wycieka pamięć, a wartości są nieznacznie różne (między innymi są generowane przez kompilator dla nazwy bieżącej funkcji).
Polecam "Wewnątrz obiektu obiektowego C++" Stanleya Lippmana, jeśli chcesz, aby można było modelować obiekty C++ (mówię, że może, ponieważ istnieje wiele sposobów implementacji elementów C++). –
Jeśli poprawisz swój kod, dlaczego nie uruchomić kompilatora z danymi wyjściowymi asemblera i zobaczyć, co generuje? –