Tek bir sanal işleve sahip olmak tüm sınıfı yavaşlatır mı?
Yoksa sadece sanal olan işleve çağrı mı? Ve sanal işlev gerçekten üzerine yazılırsa veya yazılmazsa hız etkilenir mi, yoksa sanal olduğu sürece bunun bir etkisi olmaz mı?
Sanal işlevlere sahip olmak, bir veri öğesinin daha başlatılması, kopyalanması,… böyle bir sınıftaki bir nesneyle uğraşırken, tüm sınıfı yavaşlatır. Yarım düzine kadar üyesi olan bir sınıf için fark önemsiz olmalıdır. Sadece tek bir char
üye içeren veya hiç üye içermeyen bir sınıf için fark dikkate değer olabilir.
Bunun dışında, bir sanal işleve yapılan her çağrının sanal bir işlev çağrısı olmadığına dikkat etmek önemlidir. Bilinen türde bir nesneniz varsa, derleyici normal bir işlev çağrısı için kod yayınlayabilir ve hatta böyle hissediyorsa söz konusu işlevi satır içi yapabilir. Yalnızca, temel sınıfın bir nesnesine veya türetilmiş bir sınıfın bir nesnesine işaret edebilecek bir işaretçi veya referans aracılığıyla polimorfik çağrılar yaptığınızda, vtable indireksiyonuna ihtiyaç duyarsınız ve bunun için performans açısından ödeme yaparsınız.
struct Foo { virtual ~Foo(); virtual int a() { return 1; } };
struct Bar: public Foo { int a() { return 2; } };
void f(Foo& arg) {
Foo x; x.a(); // non-virtual: always calls Foo::a()
Bar y; y.a(); // non-virtual: always calls Bar::a()
arg.a(); // virtual: must dispatch via vtable
Foo z = arg; // copy constructor Foo::Foo(const Foo&) will convert to Foo
z.a(); // non-virtual Foo::a, since z is a Foo, even if arg was not
}
Donanımın atması gereken adımlar, işlevin üzerine yazılsa da yazılmasa da esasen aynıdır. Vtable'ın adresi nesneden, uygun yuvadan alınan işlev işaretçisi ve işaretçi tarafından çağrılan işlevden okunur. Gerçek performans açısından, şube tahminlerinin bazı etkileri olabilir. Örneğin, nesnelerinizin çoğu belirli bir sanal işlevin aynı uygulamasına atıfta bulunuyorsa, dal tahmincisinin, işaretçi alınmadan önce hangi işlevi çağıracağını doğru bir şekilde tahmin etme şansı vardır. Ancak hangi işlevin ortak olduğu önemli değildir: üzerine yazılmamış temel duruma delege eden çoğu nesne veya aynı alt sınıfa ait olan ve dolayısıyla aynı üzerine yazılmış duruma delege eden çoğu nesne olabilir.
nasıl derin bir düzeyde uygulanıyor?
Bunu sahte bir uygulama kullanarak göstermek için jheriko fikrini seviyorum. Ancak yukarıdaki koda benzer bir şey uygulamak için C'yi kullanırdım, böylece düşük seviye daha kolay görülebilir.
ebeveyn sınıfı Foo
typedef struct Foo_t Foo; // forward declaration
struct slotsFoo { // list all virtual functions of Foo
const void *parentVtable; // (single) inheritance
void (*destructor)(Foo*); // virtual destructor Foo::~Foo
int (*a)(Foo*); // virtual function Foo::a
};
struct Foo_t { // class Foo
const struct slotsFoo* vtable; // each instance points to vtable
};
void destructFoo(Foo* self) { } // Foo::~Foo
int aFoo(Foo* self) { return 1; } // Foo::a()
const struct slotsFoo vtableFoo = { // only one constant table
0, // no parent class
destructFoo,
aFoo
};
void constructFoo(Foo* self) { // Foo::Foo()
self->vtable = &vtableFoo; // object points to class vtable
}
void copyConstructFoo(Foo* self,
Foo* other) { // Foo::Foo(const Foo&)
self->vtable = &vtableFoo; // don't copy from other!
}
türetilmiş sınıf Bar
typedef struct Bar_t { // class Bar
Foo base; // inherit all members of Foo
} Bar;
void destructBar(Bar* self) { } // Bar::~Bar
int aBar(Bar* self) { return 2; } // Bar::a()
const struct slotsFoo vtableBar = { // one more constant table
&vtableFoo, // can dynamic_cast to Foo
(void(*)(Foo*)) destructBar, // must cast type to avoid errors
(int(*)(Foo*)) aBar
};
void constructBar(Bar* self) { // Bar::Bar()
self->base.vtable = &vtableBar; // point to Bar vtable
}
işlev f sanal işlev çağrısı gerçekleştirme
void f(Foo* arg) { // same functionality as above
Foo x; constructFoo(&x); aFoo(&x);
Bar y; constructBar(&y); aBar(&y);
arg->vtable->a(arg); // virtual function call
Foo z; copyConstructFoo(&z, arg);
aFoo(&z);
destructFoo(&z);
destructBar(&y);
destructFoo(&x);
}
Gördüğünüz gibi, bir vtable hafızadaki statik bir bloktur ve çoğunlukla işlev işaretçileri içerir. Bir polimorfik sınıfın her nesnesi, dinamik türüne karşılık gelen vtable'a işaret edecektir. Bu aynı zamanda RTTI ile sanal işlevler arasındaki bağlantıyı daha net hale getirir: Bir sınıfın hangi tür olduğunu basitçe hangi vtable'a işaret ettiğine bakarak kontrol edebilirsiniz. Yukarıdakiler, örneğin çoklu kalıtım gibi birçok yönden basitleştirilmiştir, ancak genel kavram sağlamdır.
Eğer arg
türdeyse Foo*
ve alırsanız arg->vtable
, ancak aslında bir tür nesnesiyse Bar
, o zaman yine de vtable
. Bunun nedeni, vtable
çağrılmış vtable
veya base.vtable
doğru yazılmış bir ifadede her zaman nesnenin adresindeki ilk öğedir .
Inside the C++ Object Model
tarafındanStanley B. Lippman
. (Bölüm 4.2, sayfa 124-131)