2009-03-21 30 views
6

çağrıldı Ben Microsoft Visual C++ 2003 altında benim C++ programımı çalıştıran gerçekten sinir bozucu bir hata gibi görünüyordu, ama sadece yanlış yaptığım bir şey olabilir düşünüyorum Onu buraya atabilir ve kimsenin bir fikri olup olmadığını görürüm.C++ "this" nesne eşleşmiyor nesne yöntemi

class CWaitable 
{ 
public: 
    void WakeWaiters() const 
    { 
     CDifferentClass::Get()->DoStuff(this); // Breakpoint here 
    } 
}; 

class CMotion : public CWaitable 
{ 
    virtual void NotUsedInThisExampleButPertinentBecauseItsVirtual() { } 
}; 

class CMotionWalk : public CMotion 
{ ... }; 

void AnnoyingFunctionThatBreaks(CMotion* pMotion) 
{ 
    pMotion->WakeWaiters(); 
} 

Tamam, bu yüzden bir "CMotionWalk" örneği ile "AnnoyingFunctionThatBreaks" dediğimiz: - (gerçek kodda hiçbir çoklu miras yoktur mesela herşeyi aynı)

böyle sınıfların hiyerarşisine sahip (örneğin hata ayıklayıcı 0x06716fe0 diyor) ve hepsi iyi görünüyor. Ama içine adım attığımda, "DoStuff" çağrısı üzerindeki kesme noktasında, 'bu' işaretçisinin, yöntemi çağırdığım pMotion işaretçisine farklı bir değeri vardır (örneğin, artık hata ayıklayıcı bir sözcük daha yüksektir - 0x06716fe4).

Farklı şekilde ifade etmek için: pMotion 0x06716fe0 değerine sahiptir, ancak üzerinde bir yöntem çağırdığımda, bu yöntem 0x06716fe4 olarak görür.

Sadece sinirlenmiyorum değil mi? Bu garip, değil mi?

+0

Dilimleme, nesneler üzerinde olur ve işaretçiler değildir. Gönderdiğiniz kod, küçük bir düzeltmeden sonra çalışır. Bazı gerçek kod gönderin - kristal topum bugün çalışmıyor. BTW: Gerçekten bunu 'bu' geçmeyi düşünmüyorsun, değil mi? – dirkgently

+0

Bu neredeyse kesin olarak birden fazla kalıtımdır - kod örneği muhtemelen fazladan kırpılmıştır. –

+0

@Earwicker: Birden fazla kalıtım nereden geldi? – dirkgently

cevap

10

'a işaret eden CMotion'da, derleyicinin veterinerler tarafından oluşturulma şeklinin bir yapısını gördüğünüze inanıyorum. CMotion'un kendi sanal işlevlerine sahip olduğundan şüpheleniyorum ve böylece temel nesneye ulaşmak için türetilmiş nesne içindeki ofsetlerle sonuçlanırsınız. Böylece, farklı işaretçiler.

Çalışıyorsa (yani, bu çökelti üretmiyorsa ve nesnelerin dışında işaretçiler yoksa), bu konuda çok fazla endişelenmem.

+0

Eh, bu işe yaramıyor çünkü DoStuff yöntemi, "bu" daha önce göründüğü ile aynı değilse, kafası karışır. Ama tam olarak sizin açıkladığınız noktaya değiniyorsunuz - CMotion'un sanal fonksiyonları olduğu için soruyu ekleyeceğim, bu yüzden CWaitable :: WakeWaiters'ı problemi çözdüğü için beyan ediyoruz. – andygeers

+0

Ofsetin nasıl değişebileceğini anlamıyorum. türetilmiş sınıf bir temel sınıfa sahiptir ve her ikisi de bellekte aynı noktada başlar. Neden toprak birden fazla miras olmadan ofset farklı olmalı? msvc garip bir şey mi yapıyor? –

+0

MSVC'nin bellekte nasıl bir şey oluşturduğuna dair hiçbir fikrim yok, ancak bunun hakkında düşünürseniz, Base nesnesinin başladığı Türetilmiş nesnenin içinde bir yerde olması gerekir. Derleyicinin kaprisine bağlı olarak, türetilmiş öğeleri temel öğelerinden ÖNCE veya SONRA koymak mümkündür. –

2

Ayrıca bkz. wikipedia article on thunking. Debugger'ı derleme kodundan geçirecek şekilde ayarlarsanız, bunun gerçekleştiğini görmelisiniz. (bu bir thunk ya da sadece ofset değiştirerek verdiğiniz koddan elinizdeki ayrıntılara bağlıdır)

+0

Montaj kodunu değiştirmeyi denedim. Gerçekten ne aradığımı bilmiyorum, ama bu olağandışı görünmüyordu – andygeers

0

Bazı gerçek kodları göndermeniz gerekir. beklenmektedir aşağıdaki olarak işaretçileri için değerler - Aynı olan diğer bir deyişle:

#include <iostream> 
using namespace std; 

struct A { 
    char x[100]; 
    void pt() { 
     cout << "In A::pt this = " << this << endl; 
    } 
}; 

struct B : public A { 
    char z[100]; 
}; 

void f(A * a) { 
    cout << "In f ptr = " << a << endl; 
    a->pt(); 
} 

int main() { 
    B b; 
    f(&b); 
} 
6

CMotion sınıfı, bir sanal fonksiyonu içeren, aynı zamanda başka bir sınıf türetmek var mı?

class Test 
{ 
public: 
    virtual void f() 
    { 

    } 
}; 

class CWaitable 
{ 
public: 
    void WakeWaiters() const 
    { 
     const CWaitable* p = this; 
    } 
}; 

class CMotion : public CWaitable, Test 
{ }; 


class CMotionWalk : public CMotion 
{ 
public: 
}; 



void AnnoyingFunctionThatBreaks(CMotion* pMotion) 
{ 
    pMotion->WakeWaiters(); 
} 

Bunun nedeni CMotion sınıf için birden miras ve vtable işaretçi olduğuna inanıyorum: Ben böyle hiyerarşi şey varsa ancak değiştirir, bu işaretçi deftere kodu ile değişmediğini tespit CMotion'da, Test :: f()

+0

Kesinlikle birden fazla kalıtım yok. – andygeers

0

bu çalışır, ancak neden açıklayamam sanal düzeltmeler olarak ilan CWaitable :: WakeWaiters ben bunu açıkladıklarını zannetmektedirler konu

1

... daha iyi bir açıklama ya Meyer birinde orada bir yerde ya da Sutter'in kitapları, ama arama gibi hissetmedim. Gördüğünüzün, sanal fonksiyonların nasıl uygulandığı (vtables) ve "siz onu kullanana kadar bunun için ödeme yapmamanın" bir sonucu olduğunu düşünüyorum.

Kullanımda sanal yöntem yoksa, nesneye yönelik bir işaretçi nesnenin verilerine işaret eder. Sanal bir yöntem sunulduğunda, derleyici sanal bir arama tablosu (vtable) ekler ve işaretçi bunun yerine işaret eder. Muhtemelen bir şey kaçırıyorum (ve beynim henüz çalışmıyor) çünkü bunu temel sınıfa bir veri üyesi ekleyene kadar bunu gerçekleştiremedim. Temel sınıf bir veri üyesine sahipse ve birinci alt sınıf bir sanal içeriyorsa, ofsetler vtable boyutuna göre değişir (derleyicimde 4).İşte bu açıkça gösteren bir örneği: benim makinede bu Running

template <typename T> 
void displayAddress(char const* meth, T const* ptr) { 
    std::printf("%s - this = %08lx\n", static_cast<unsigned long>(ptr)); 
    std::printf("%s - typeid(T).name() %s\n", typeid(T).name()); 
    std::printf("%s - typeid(*ptr).name() %s\n", typeid(*ptr).name()); 
} 

struct A { 
    char byte; 
    void f() { displayAddress("A::f", this); } 
}; 
struct B: A { 
    virtual void v() { displayAddress("B::v", this); } 
    virtual void x() { displayAddress("B::x", this); } 
}; 
struct C: B { 
    virtual void v() { displayAddress("C::v", this); } 
}; 

int main() { 
    A aObj; 
    B bObj; 
    C cObj; 

    std::printf("aObj:\n"); 
    aObj.f(); 

    std::printf("\nbObj:\n"); 
    bObj.f(); 
    bObj.v(); 
    bObj.x(); 

    std::printf("\ncObj:\n"); 
    cObj.f(); 
    cObj.v(); 
    cObj.x(); 

    return 0; 
} 

(MacBook Pro) aşağıdaki yazdırır:

aObj: 
A::f - this = bffff93f 
A::f - typeid(T)::name() = 1A 
A::f - typeid(*ptr)::name() = 1A 

bObj: 
A::f - this = bffff938 
A::f - typeid(T)::name() = 1A 
A::f - typeid(*ptr)::name() = 1A 
B::v - this = bffff934 
B::v - typeid(T)::name() = 1B 
B::v - typeid(*ptr)::name() = 1B 
B::x - this = bffff934 
B::x - typeid(T)::name() = 1B 
B::x - typeid(*ptr)::name() = 1B 

cObj: 
A::f - this = bffff930 
A::f - typeid(T)::name() = 1A 
A::f - typeid(*ptr)::name() = 1A 
C::v - this = bffff92c 
C::v - typeid(T)::name() = 1C 
C::v - typeid(*ptr)::name() = 1C 
B::x - this = bffff92c 
B::x - typeid(T)::name() = 1B 
B::x - typeid(*ptr)::name() = 1C 

ilginç şey olduğunu hem bObj ve cObj sergi arasındaki olarak adres değişikliği A ve B veya C numaralı telefonlarda arama yöntemleri. Fark, B'un bir sanal yöntem içermesidir. Bu, derleyicinin işlev sanallaştırmasını uygulamak için gerekli ek tabloyu eklemesine izin verir. Bu programın gösterdiği diğer ilginç şey, typeid(T) ve typeid(*ptr)'un hemen hemen çağrıldığında B::x'da farklı olmasıdır. Ayrıca sanal tablo eklendiğinde, sizeof kullanarak boyut artışını görebilirsiniz.

Sizin durumunuzda, CWaitable::WakeWaiters sanal gerçekleştirdiğiniz anda, vtable eklenir ve aslında gerekli olan defter tutma yapılarının eklenmesiyle nesnenin gerçek tipine dikkat eder. Bu, nesnenin tabanına olan ofsetin farklı olmasına neden olur. Efsanevi bir bellek düzenini tanımlayan referansı ve bir nesnenin adresinin mirasın eğlenceye karıştığı zamanki yorumlanmasına neden bağlı olduğunu bulmayı gerçekten çok isterdim.

Genel Kural: (ve bu duymuş önce) baz sınıfları hep sanal yıkıcı var. Bu, böyle küçük sürprizleri ortadan kaldırmaya yardımcı olacaktır.

+0

Buradaki fikir, her sınıfın kendi verilerini bir bellek belleğinde saklı tutması ve yöntemlerinin, bu verilere erişimin olabildiğince hızlı olacağı şekilde üretilmesidir. Bu nedenle, bu yöntemler için "bu" işaretçi aynı bağlam içinde değiştirmek zorunda kalmaz. –