2010-07-27 10 views
14

मैं वर्तमान में एक गैर RTTI मंच (Android) पर RTTI सामान के बहुत सारे का उपयोग करता है एक तीसरे पक्ष के पैकेज एकीकरण पर काम कर रहा हूँ बिना एक हीरे की आकृति विरासत में मिला वस्तु downcasting। असल में, मैंने अपना खुद का आरटीटीआई कार्यान्वयन किया लेकिन मैं एक समस्या पर फंस गया हूं।सी ++ - RTTI/dynamic_cast

मुद्दा यह है कि बहुत से वर्गों में हीरा विरासत समस्या होती है क्योंकि सभी वर्ग समान आधार वर्ग (वस्तु) से प्राप्त होते हैं .. और इसलिए, यदि मैं बेस क्लास से व्युत्पन्न कक्षा में उतरना चाहता हूं, मुझे गतिशील_कास्ट का उपयोग करना है - लेकिन आरटीटीआई उपलब्ध नहीं है! गतिशील_कास्ट के बिना वर्चुअल विरासत होने पर मैं ऑब्जेक्ट को पैरेंट से बच्चे में कैसे परिवर्तित करूं?

ऐसा नहीं है कि तरह लग रहा है:

class A 
{ 
public: 
virtual char* func() { return "A"; }; 
}; 
class B : public virtual A 
{ 
public: 
//virtual char* func() { return "B"; }; 
}; 
class C : public virtual A 
{ 
public: 
//virtual char* func() { return "C"; }; 
}; 

class D : public B, public C 
{ 
public: 
//virtual char* func() { return "D"; }; 
}; 

D d; 
A* pa = static_cast<A*>(&d); 
D* pd = static_cast<D*>(pa); // can't do that! dynamic_cast does work though... 

उन मेरी त्रुटियाँ हैं:

त्रुटि C2635: एक 'डी *' के लिए परिवर्तित नहीं कर सकते एक 'ए *'; एक आभासी आधार वर्ग से रूपांतरण

निहित है

त्रुटि C2440: 'आरंभ': 'test_convert :: डी *' आधार से कास्ट करने के लिए 'test_convert :: एक *' से परिवर्तित नहीं कर सकते प्राप्त होने वाले dynamic_cast की आवश्यकता है या static_cast

कोई भी विचार?

+0

हे, अच्छी तरह से एमएस कोड से वर्चुअल कीवर्ड को हटाने के लिए कहता है और यह समस्या को हल करेगा। त्रुटि के लिए उनके दस्तावेज़ देखें, मैं मजाक नहीं कर रहा हूँ। –

+1

एंड्रॉइड ने आपको बताया कि एंड्रॉइड एक गैर-आरटीआई मंच है? आर 5 और नए एनडीके को आरटीटीआई का समर्थन करना चाहिए (मुझे विश्वास है कि आपको इसे '-फ्रेटी' के साथ चालू करने की आवश्यकता है, लेकिन इसे तब काम करना चाहिए)। पुराने प्लेटफ़ॉर्म के लिए भी, क्योंकि यह सभी स्थिर रूप से जुड़ा हुआ है। –

उत्तर

12

आप केवल dynamic_cast के साथ इस डाली कर सकते हैं; कोई अन्य कलाकार ऐसा नहीं करेगा।

आप अपने इंटरफेस डिजाइन नहीं कर सकते हैं ताकि आप कलाकारों के इस प्रकार प्रदर्शन करने की जरूरत नहीं है तो केवल एक चीज आप कर सकते हैं अपने वर्ग पदानुक्रम की ढलाई कार्यक्षमता हिस्सा बनाने के लिए है।

उदा। (बुरी तरह hacky)

class D; 

class A 
{ 
public: 
    virtual D* GetDPtr() { return 0; } 
}; 

class B : public virtual A 
{ 
}; 

class C : public virtual A 
{ 
}; 

class D : public B, public C 
{ 
public: 
    virtual D* GetDPtr() { return this; } 
}; 
+3

मुझे नहीं पता कि कम से कम क्या है या इस उत्तर के लिए जोड़ें। –

+2

@ नोहा: मेरी रक्षा में, मैंने "भयंकर हैकी" कहा था! –

+2

@ नोहा: हैकी संस्करण लंबे समय पहले एक परियोजना पर मैंने देखा है कि एक कास्ट मैट्रिक्स से बेहतर दिखता है। आरटीआई-कम सिस्टम पर लोगों को प्रत्येक वर्ग को एक अद्वितीय आईडी देने और आईडी को दो आयाम मैट्रिक्स में इंडेक्स के रूप में उपयोग करने से बेहतर कुछ नहीं मिला। मैट्रिक्स [cast_what] [cast_to] कक्षा का एक आईडी था, गतिशील_कास्ट का परिणाम। – Dummy00001

-2

जब तक आप एक और तरीका है सुनिश्चित करें कि आप क्या कर रहे हैं बनाने के लिए है के रूप में कार्यावधि में सुरक्षित टाइप करें, सिर्फ reinterpret_cast इस्तेमाल करते हैं।

यह मूल रूप से सी शैली के कलाकार के समान ही है, इसलिए यदि आपको करना है तो केवल इसका उपयोग करें, लेकिन यह उपरोक्त कोड को संकलित करने की अनुमति देगा।

+1

ठीक है, मैंने कोशिश की है लेकिन मुझे एक चेतावनी संदेश मिला है (4 9 46: reinterpret_cast संबंधित वर्गों के बीच उपयोग किया जाता है: 'class1' और 'class2')। ध्यान दें कि चेतावनी डिफ़ॉल्ट रूप से अक्षम है। मेरा मानना ​​है कि reinterpret_cast सिर्फ पॉइंटर को पुन: परिभाषित करता है (जैसा कि यह कहता है) लेकिन यह इसे नहीं डाला जाता है और यह तब होता है जब वर्चुअल उत्तराधिकारी या एकाधिक माता-पिता होते हैं क्योंकि एक माता-पिता या किसी अन्य को कास्टिंग करने से पॉइंटर के मान को संशोधित किया जा सकता है। इस प्रकार, reinterpret_cast मेरे मामले में काम नहीं करेगा जहां मैं उपयोग की तीसरी पार्टी उन अवधारणाओं (एकाधिक माता-पिता, विषाणु विरासत, आदि) के व्यापक उपयोग करता है। – Adam

3

ज्यादातर मामलों में आगंतुक पैटर्न downcasts से बचने के लिए इस्तेमाल किया जा सकता। इसका उपयोग गतिशील_कास्ट से बचने के लिए भी किया जा सकता है।

कुछ चेतावनियां:

1) यह अपमानजनक वर्गों को बदलना संभव होना चाहिए।
2) आप हर व्युत्पन्न वर्ग पता करने की जरूरत हो सकती है।
3) वस्तुओं को कम से कम बेसक्लास से प्राप्त करने के लिए जाना जाना चाहिए, आप पूरी तरह से असंबंधित प्रकारों को कास्ट करने की कोशिश नहीं कर सकते हैं। (ऐसा लगता है: "मैं बेस क्लास से व्युत्पन्न कक्षा में डाउनकास्ट करना चाहता हूं")

निम्नलिखित उदाहरण में मैंने टेम्पलेट का उपयोग किया। इन्हें आसानी से छुटकारा मिल सकता है, लेकिन कुछ लेखन प्रयासों की आवश्यकता होगी।

class A; 
class B; 
class C; 
class D; 

// completely abstract Visitor-baseclass. 
// each visit-method must return whether it handled the object 
class Visitor 
{ 
public: 
    virtual bool visit(A&) = 0; 
    virtual bool visit(B&) = 0; 
    virtual bool visit(C&) = 0; 
    virtual bool visit(D&) = 0; 
}; 

class A 
{ 
public: 
    virtual const char* func() { return "A"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 
class B : public virtual A 
{ 
public: 
    virtual const char* func() { return "B"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 
class C : public virtual A 
{ 
public: 
    virtual const char* func() { return "C"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 
class D : public B, public C 
{ 
public: 
    virtual const char* func() { return "D"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 

// implementation-superclass for visitors: 
// each visit-method is implemented and calls the visit-method with the parent-type(s) 
class InheritanceVisitor : public Visitor 
{ 
    virtual bool visit(A& a) { return false; } 
    virtual bool visit(B& b) { return visit(static_cast<A&>(b)); } 
    virtual bool visit(C& c) { return visit(static_cast<A&>(c)); } 
    virtual bool visit(D& d) { return visit(static_cast<B&>(d)) || visit(static_cast<C&>(d)); } 
}; 

template<typename T> // T must derive from A 
class DerivedCastVisitor : public InheritanceVisitor 
{ 
public: 
    DerivedCastVisitor(T*& casted) : m_casted(casted) {} 
    virtual bool visit(T& t) 
    { m_casted = &t; return true; } 
private: 
    T*& m_casted; 
}; 

// If obj is derived from type T, then obj is casted to T* and returned. 
// Else NULL is returned. 
template<typename T> 
T* derived_cast(A* obj) 
{ 
    T* t = NULL; 
    if (obj) 
    { 
    DerivedCastVisitor<T> visitor(t); 
    obj->accept(visitor); 
    } 
    return t; 
} 

int main(int argc, char** argv) 
{ 
    std::auto_ptr<A> a(new A); 
    std::auto_ptr<A> b(new B); 
    std::auto_ptr<A> c(new C); 
    std::auto_ptr<A> d(new D); 

    assert(derived_cast<A>(a.get()) != NULL); // a has exact type A 
    assert(derived_cast<B>(b.get()) != NULL); // b has exact type B 
    assert(derived_cast<A>(b.get()) != NULL); // b is derived of A 
    assert(derived_cast<C>(b.get()) == NULL); // b is not derived of C 
    assert(derived_cast<D>(d.get()) != NULL); // d has exact type D 
    assert(derived_cast<B>(d.get()) != NULL); // d is derived of B 
    assert(derived_cast<C>(d.get()) != NULL); // d is derived of C, too 
    assert(derived_cast<D>(c.get()) == NULL); // c is not derived of D 

    return 0; 
} 
0

आभासी विरासत के साथ समस्या यह है कि आधार वर्ग पता जरूरी नहीं है व्युत्पन्न पते के रूप में ही है। इस प्रकार, reinterpret_cast या void* कास्ट मदद नहीं करेगा।

dynamic_cast का उपयोग किए बिना इसे हल करने का एक तरीका है कलाकार के अनुसार ऑब्जेक्ट पते को संशोधित करने के लिए दोनों सूचक प्रकार (सटीक प्रकार और रेफ प्रकार) के बीच ऑफसेट की गणना करना है।

template <typename E, typename T> 
E& force_exact(const T& ref) 
{ 
    static const E* exact_obj; 
    static const T& exact_obj_ref = *exact_obj; 
    static const ptrdiff_t exact_offset = 
    (const char*)(void*)(&exact_obj_ref) 
    - (const char*)(void*)(exact_obj); 
    return *(E*)((char*)(&ref) - exact_offset); 
} 
+0

साइड नोट: ऑफसेट की घोषणा स्थैतिक बना दी गई है क्योंकि इसे सिद्धांत में प्रत्येक जोड़े टी, ई के लिए केवल एक बार गणना की जानी चाहिए। हालांकि अभ्यास में, 'स्थिर' का उपयोग करके एक रनटाइम लागत होती है ('if (! Var_initilized) var_ = ...') के रूप में पुनः लिखा गया है। इससे बाहर निकलने का एक तरीका यह है कि एक टेम्पलेट क्लास को तुरंत चालू करें जो प्रत्येक जोड़े टी, ई के लिए ऑफसेट मान को स्थिर रूप से संग्रहीत करता है लेकिन यह एक और कहानी है ... – log0

1

कोड:

template <typename E, typename T> 
E& force_exact(const T& ref) 
{ 
    static const E* exact_obj; 
    static const T& exact_obj_ref = *exact_obj; 
    static const ptrdiff_t exact_offset = ... 

static const E* exact_obj के रूप में मेरे लिए बहुत अच्छी तरह से काम नहीं करता है शून्य है, इसलिए स्थैतिक const T& exact_obj_ref = *exact_obj derefs शून्य, भी है, और इस प्रकार static const ptrdiff_t exact_offset भी हो जाता है शून्य।

ऐसा लगता है कि व्युत्पन्न वर्ग को तत्काल होना आवश्यक है (जो सार वर्गों के लिए एक समस्या हो सकती है ...)। तो मेरी कोड है:

template <typename D, typename B> 
D & Cast2Derived(B & b) 
{ static D d; 
    static D * pD = & d; 
    static B * pB = pD; 
    static ptrdiff_t off = (char *) pB - (char *) pD; 

    return * (D *) ((char *) & b - off); 
} 

MSVC 2008, WinXP 32b के तहत परीक्षण किया गया।

कोई टिप्पणी/बेहतर समाधान स्वागत है।

Lup

+0

यह समस्याग्रस्त है यदि डी के निर्माता दुष्प्रभाव उत्पन्न करते हैं। शायद बेहतर समाधान डी पॉइंटर को एक उचित आकार और गठबंधन चार बफर reinterpret_cast करना है। साथ ही, सी ++ कास्टिंग नाम संयोजनों को रखने के लिए, इसे वर्चुअल_कास्ट (बी *) 'नाम देने के बारे में कैसे? मैंने अभी तक इसे गहरा विचार नहीं दिया है, लेकिन क्या कुछ विरासत पदानुक्रम हैं कि यह असफल रहेगा? शायद एक वर्ग जो एक ही वर्चुअल क्लास से कई बार विरासत में है (भले ही अप्रत्यक्ष रूप से) या शायद बूट करने के लिए गैर वर्चुअल विरासत के संयोजन के साथ। किसी भी तरह +1 –

+0

हाय थॉमस, क्या आप कृपया डी * को कास्टिंग चार बफर को दोबारा परिभाषित करने के बारे में अपने विचार को बेहतर तरीके से वर्णन कर सकते हैं? – Juster

4

एंड्रॉयड समर्थन RTTI करता है। आपको नवीनतम एनडीके की आवश्यकता है (कम से कम आर 5, नवीनतम आर 6 है) और आपको डिफ़ॉल्ट के बजाय जीएनयू stdlibC++ के खिलाफ संकलन करने की आवश्यकता है।

इससे पहले भी, क्रिस्टाक्स के पुनर्निर्माण ने समर्थन अपवाद और आरटीआईआई (हमें आधिकारिक एनडीके आर 5 सी तक इसका उपयोग करना पड़ा क्योंकि आर 5 ए और आर 5 बी का समर्थन था, लेकिन पुराने (प्री-2.3) सिस्टम पर दुर्घटनाग्रस्त हो गया था)।

पीएस: किसी को वास्तव में विक्रेताओं को यह कहना चाहिए कि वे सी ++ का समर्थन करते हैं, जब वे अपवाद और आरटीआई का समर्थन नहीं करते हैं, क्योंकि अधिकांश मानक लाइब्रेरी, और यह सी ++ मानक का हिस्सा है, बिना किसी काम के काम करता है। इसके अलावा उनका समर्थन नहीं करना मूर्खतापूर्ण है, विशेष रूप से अपवादों के लिए, क्योंकि अपवाद वाले कोड बिना किसी से अधिक कुशल होते हैं (बशर्ते वे असाधारण मामलों को सिग्नल करने के लिए उचित रूप से उपयोग किए जाते हैं)।