2010-03-04 7 views
7

Programım, dinamik başlatma durumunda veri veya nesneleri taşımak için void *' den faydalanmak zorundadır, böylece ilkel olsa bile rasgele türlere ait verileri referans gösterebilir türleri. Ancak, yakın zamanda, çok sayıda temel sınıfları olan sınıflar söz konusu olduğunda bu boşluğu azaltma işleminin başarısız olduğunu ve hatta bellek adresleri doğru gibi görünse bile bu aşağı dökümlü işaretçilerdeki yöntemleri çağırdıktan sonra programımı çökerttiğini keşfettim. Kaza, "vtable" erişim sırasında gerçekleşir.çoklu kalıtım: beklenmedik sonuç void * 'den 2. temel sınıfa dönüldükten sonra

Yani küçük bir test durumu yarattık, çevre Mac OS X üzerinde 4.2 gcc geçerli: Gördüğünüz gibi

Decorated (direct)30,30 
Shape 30,30 
Square 30,30 
Decorated (per void*) 73952,73952 
DecoratedSquare 30,30 

, "dekore (:

class Shape { 
public: 
    virtual int w() = 0; 
    virtual int h() = 0; 
}; 

class Square : public Shape { 
public: 
    int l; 
    int w() {return l;} 
    int h() {return l;} 
}; 

class Decorated { 
public: 
    int padding; 
    int w() {return 2*padding;} 
    int h() {return 2*padding;} 
}; 

class DecoratedSquare : public Square, public Decorated { 
public: 
    int w() {return Square::w() + Decorated::w();} 
    int h() {return Square::h() + Decorated::h();} 
}; 


#include <iostream> 

template <class T> T shape_cast(void *vp) { 
// return dynamic_cast<T>(vp); // not possible, no pointer to class type 
// return static_cast<T>(vp); 
// return T(vp); 
// return (T)vp; 
    return reinterpret_cast<T>(vp); 
} 

int main(int argc, char *argv[]) { 
    DecoratedSquare *ds = new DecoratedSquare; 
    ds->l = 20; 
    ds->padding = 5; 
    void *dsvp = ds; 

    std::cout << "Decorated (direct)" << ds->w() << "," << ds->h() << std::endl; 

    std::cout << "Shape " << shape_cast<Shape*>(dsvp)->w() << "," << shape_cast<Shape*>(dsvp)->h() << std::endl; 
    std::cout << "Square " << shape_cast<Square*>(dsvp)->w() << "," << shape_cast<Square*>(dsvp)->h() << std::endl; 
    std::cout << "Decorated (per void*) " << shape_cast<Decorated*>(dsvp)->w() << "," << shape_cast<Decorated*>(dsvp)->h() << std::endl; 
    std::cout << "DecoratedSquare " << shape_cast<DecoratedSquare*>(dsvp)->w() << "," << shape_cast<DecoratedSquare*>(dsvp)->h() << std::endl; 
} 

şu çıktıyı üretir void *) "sonuç tamamen yanlıştır. İlk satırda olduğu gibi 30,30 olmalıdır.

Shape_cast() 'de kullandığım kalıplama yöntemi ne olursa olsun, her zaman Dekorasyon bölümünde kullanılanla aynı beklenmedik sonuçları elde ederim. Bu boşluk ile ilgili bir şey tamamen yanlıştır *.

C++ konusundaki anlayışımdan bu aslında çalışmalıdır. Bunu boşluğa * çalışmak için bir şans var mı? Bu gcc'de bir hata olabilir mi?

sayesinde

+1

Sadece reinterpret_cast öğesini void * 'e çevirmek için ve sonra da orijinal türüne geri döndürebilirsiniz. __NOT__, * ve sonra başka bir şeye çevirme işlemini gerçekleştirebilir. –

+0

Dekoratör kalıbını MI'dan daha iyi kullanabilirsiniz; bu, dökümün void * probleminden çözülmesini sağlamaz. – quamrana

cevap

7

Bu bir derleyici hatası değil - reinterpret_cast'un yaptığı şey. DecoratedSquare nesne böyle hafıza şey düzenlenir:

Square 
Decorated 
DecoratedSquare specific stuff 

orada ne tür hiçbir bilgi ile, bu verilerin başlangıç ​​adresi verecek void* bu bir gösterici dönüştürülüyor. reinterpret_cast<Decorated*> bu adresi alacak ve Decorated olarak ne varsa yorumlayacaktır - ancak asıl bellek içeriği Square'dur. Bu yanlış, bu yüzden tanımlanmamış davranışlara sahipsiniz.

reinterpret_cast doğru dinamik türüne (DecoratedSquare) sahipseniz doğru sonuçları almalısınız, ardından temel sınıfa dönüştürün.

+0

Bunu açıklayan, ancak bu, alt sınıf bilgisini bilmeden, arayan kişinin olmadığı tek miras durumlarında mümkün olabileceği gibi polimorfik bir şekilde temel sınıflardan birine işaret eden kod üretemediğim anlamına gelir Beton alt sınıfını bilmek gerekir. Diğer bir deyişle, bir çerçeveyi derlemek ve daha sonra somut alt sınıflar bilindiğinde kullanmak mümkün olmaz mı? Bu, C++ 'da çoklu kalıtımın tamamlanmadığı anlamına gelir. Tür bilgisini atmadığınız sürece C++ 'da –

+2

MI tamamlandı. Yanlış tiplere çıldırmazsanız, somut alt sınıfları bilmeden polimorfik davranışlar elde etmek için 'sanal' fonksiyonlarını kullanabilirsiniz. Sadece çerçevenin bu bölümünde beton alt türünü bilmiyorum olarak Bana göre –

+0

, sadece ben * orada boşluk kullanamazsınız anlamına gelir. Bir şekilde "herhangi" veya benzerlerini kullanarak tür bilgilerini taşımalıyım. Güzel değil ve beklediğimden değil :(ama en azından bana iyi bir açıklama verdin.Teşekkürler herkese –

2

bir static_cast veya doğru adresi belirlemek, böylece bu dengelenerek ibrenin temsil değişebilir çoklu miras varlığında bir dynamic_cast. static_cast, statik yazım bilgilerini dikkate alarak doğru ofseti belirler. dynamic_cast dinamik türü kontrol ederek yapar. Boşluk atıyorsanız *, tüm statik yazım bilgilerini ve dinamik yazım bilgilerini alma olasılığınızı kaybedersiniz, bu nedenle kullandığınız reinterpret_cast ofsetin null olduğunu ve bazı zamanların başarısız olduğunu varsayar.

+0

, bu, void * 'in birden fazla miras durumunda mümkün olmadığı anlamına mı geliyor? –

+1

Her zaman, nesnenin gerçek türüne, temel bir sınıfa atmadığınız sürece, bu mümkündür. –

+0

Mike'ın cevabı hakkındaki yorumumu gör –

9

On kez tekrar edin - reinterpret_cast işaretçisiyle güvenle yapabileceğiniz tek şey, geldiği aynı işaretçi türüne geri reinterpret_cast olmasıdır. Aynı şey void* numaralı dönüşümler için de geçerlidir: orijinal türüne geri dönüş yapmanız gerekir.

DecoratedSquare* değerini void* atarsanız, DecoratedSquare*'a geri göndermeniz gerekir. Decorated* değil, Square* değil, Shape*. Bazıları makineniz üzerinde çalışabilir, ancak bu iyi şanslar ve uygulamaya özgü davranışların birleşimidir.Genellikle orada çalışan durdurmak edecek bir şekilde nesne işaretçileri uygulamak için makul bir neden, ama bu garanti edilmez ve çoklu kalıtım için genel olarak çalışamaz, çünkü tek miras ile çalışır.

Kodunuzun bir boşlukla * "ilkel türler de dahil olmak üzere keyfi türlere" eriştiğini söylüyorsunuz. Bu konuda yanlış bir şey yok - muhtemelen veriyi alan her kimse bunu DecoratedSquare* olarak kabul etmeyi bilir ve int* olarak değil. sadece, bir temel sınıf olarak örneğin Decorated* bunu tedavi etmek bilir kim alır sonra her kim void* dönüştürür void*, sonra ilk temel sınıf için static_cast olursa

:

void *decorated_vp = static_cast<Decorated*>(ds); 

Şimdi zaman Eğer Decorated* için decorated_vp geri dökme, size ne gerek olan static_cast<Decorated*>(ds) sonucunu alırsınız.

+0

Steve Bunun için teşekkürler. Ben çok özetliyor. Gerçekten de, çerçevenin çağıran kısmında, boşluğa * dökülen ve daha sonra 2 şeyi bilen alıcı uca nakledilen bilinen bir temel işaretleyici tipi vardır: en üstteki temel sınıf ve bir metodun bulunduğu bazı alt sınıflar. tanımlanmış. Bununla birlikte, beton alt sınıfı bilmemektedir. Ancak bu, alıcı taraf her zaman, gönderen tarafın yaptığı gibi en üstteki temel sınıfa yeniden reinterpret_cast'ler eklediğinden ve sonra istenen yöntemi çağırdığı ara alt sınıfa bir dynamic_cast veya static_cast yaparsa, bu durum iyi görünüyor. –

+0

+1, "reinterpret_cast işaretçisi ile güvenle yapabileceğiniz tek şey, yeniden geldiği aynı işaretçi türüne yeniden yazılmasıdır." Durumu çok iyi özetler. –

İlgili konular