2009-07-06 18 views
22

Aşağıdaki C++ kodunu göz önünde bulundurun:İşaretçiler sanal üye işlevlerine. O nasıl çalışır?

class A 
{ 
public: 
     virtual void f()=0; 
}; 


int main() 
{ 
    void (A::*f)()=&A::f; 
} 

Tahmin etmem gerekirse, & A :: f bu bağlamda "a (f) 'nin (A) uygulanmasının adresi" Düzenli üye işlevleri ve sanal üye işlevleri için işaretçiler arasında açık bir ayrım yoktur. Ve A, f() öğesini uygulayamadığı için, bu bir derleme hatası olur. Ancak, öyle değil.

Ve sadece bu değil. Aşağıdaki kod:

void (A::*f)()=&A::f; 
A *a=new B;   // B is a subclass of A, which implements f() 
(a->*f)(); 

aslında B :: f.

Bu nasıl olur?

+0

Çünkü derleyici bunu yapar! Normal bir yöntemi çağırmak, sanal bir yöntemi çağırmaktan farklı değilse, yöntem işaretçisini kullanırken kodun neden farklı olduğunu düşünüyorsunuz? Derleyicinin normal yöntem (sanal ve ordingary) çağrılarını nasıl çevirdiğini düşünüyorsunuz? –

cevap

9

Burada üye işlev işaretçileri hakkında çok fazla bilgi var. Makalenin gerçekten C++ 'daki delegeleri uygulamakla ilgili olduğunu düşündüğüm makaleyi okuduğumda, IIRC'nin bu bölümü gözden geçirdiğimde, "İyi İşlenmiş Derleyiciler" altında sanal işlevler hakkında bazı şeyler var.

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

Kısa cevap

o derleyici bağlıdır, ancak bir olasılık üye işlev işaretçisi sanal çağrı yapan bir "thunk" işlev işaretçisi içeren bir yapı olarak uygulandığını olmasıdır.

+0

merhaba, sadece thunk hakkında işaret etti. Orada thunk açıklayan bazı iyi makale var mı? – anand

+0

"thunk sözcüğü, genellikle belirli bir yazılım sisteminin bazı detaylarını uygulayan, makine tarafından üretilen düşük seviyeli kodun bir parçasını ifade eder.", Http://en.wikipedia.org/wiki/Thunk. Bu sayfa, bu belirli bir şey olmamasına rağmen, birkaç çeşit hatıraları tartışır. –

22

Çünkü Standart, bunun nasıl olması gerektiğini söylüyor. GCC ile bazı testler yaptım ve sanal işlevler için çıktı, GCC söz konusu işlevin sanal tablosunu bayt cinsinden depolar.

struct A { virtual void f() { } virtual void g() { } }; 
int main() { 
    union insp { 
    void (A::*pf)(); 
    ptrdiff_t pd[2]; 
    }; 
    insp p[] = { { &A::f }, { &A::g } }; 
    std::cout << p[0].pd[0] << " " 
      << p[1].pd[0] << std::endl; 
} 

O program çıktıları 1 5 - bu iki fonksiyonlarının sanal masa girdilerinin bayt uzaklıkları. Bu, Itanium C++ ABI, which specifies that'u takip eder.

+0

Sorunun cevabının C++ standardize olmadığını varsayalım. Ancak, vtables, ancak sanal işlevler için mekanizma olarak vtables kullanmaz herhangi bir derleyici bilmiyorum, bu yüzden de bunun için bir * standart * mekanizma olduğunu varsayalım. Cevabınız sadece beni daha da karıştırıyor.Derleyici, 1 ve 5'i A üye işlevlerine işaretçiler olarak kaydederse, bunun geçerli bir dizin mi yoksa gerçek bir adres mi olduğunu nasıl anlayabiliriz? (düzenli ve sanal üye fonksiyonlarına işaretçiler arasında fark olmadığını unutmayın) –

+0

Bu cevap neden daha fazla kafa karıştırıyor? Sadece bir şey açık değilse sorun. Bu tür bir şey standart değil. Bunu çözmenin yollarını düşünmek, uygulamaya kalmıştır. Bir işlev işaretçisi olup olmadığına karar verebilirler: Bence bu yüzden 1 eklediler. Bu nedenle, numara hizalanmamışsa, vtable ofset. Hizalıysa, üye işlevinin bir göstergesidir. Yine de bu benim için bir tahmin. –

+0

Teşekkürler, bu mantıklı geliyor. Ancak, bir C++ uygulaması için biraz verimsiz ... VC'deki kodu kontrol ettim ve sonuçlar tamamen farklı. Çıktı, bir şeyin adresi gibi görünen 'c01380 c01390'. –

1

Tamamen emin değilim, ama bence bu normal polimorfik davranış. &A::f'un aslında sınıfın vtable'ındaki işlev göstericisinin adresi olduğunu düşünüyorum ve bu yüzden derleyici hatası almıyorsunuz. Vtable'daki alan hala tahsis edilmiş ve aslında geri döndüğünüz yer burası.

Bu, anlamlıdır çünkü türetilmiş sınıflar, temel olarak bu değerlerin işlevlerine ilişkin işaretçilerle üzerine yazılır. Bu nedenle, (a->*f)() ikinci örneğinizde çalışmaktadır - f türetilmiş sınıfta uygulanan vtable öğesine başvuruyor.

+1

Normal üye işlevleri için işaretçiler ve sanal üye işlevlerine ilişkin işaretçiler arasında bir ayrım varsa bu durum söz konusu olabilirdi. Ancak, bahsettiğim gibi, yok, ve hepsi bu. –

+0

Bir derleyicinin sanal olarak veya vtable'da olmayan tüm yöntemleri koymasına kesinlikle izin verilir. Bunu yaparsa, üye işlevlerine işaretçiler için vtable dizinini kullanabilir. Aslında derleyici için oldukça basit - sadece sanal olmayan bir programcının temel sınıf girişinin üzerine yazmak yerine kendi vtable girişini aldığından emin olun. – MSalters