2010-07-27 11 views
14

Ich arbeite derzeit an der Integration eines Pakets von Drittanbietern, das viele RTTI-Sachen auf einer Nicht-RTTI-Plattform (Android) verwendet. Grundsätzlich habe ich meine eigene RTTI-Implementierung gemacht, aber ich stecke auf einem Problem fest.C++ - Downcasting eines Diamond Shape geerbten Objekts ohne RTTI/dynamic_cast

Das Problem ist, dass viele Klassen das Problem der Diamantvererbung haben, da alle Klassen von derselben Basisklasse (Objekt) abgeleitet sind. Wenn ich also von der Basisklasse in die abgeleitete Klasse umwandeln möchte, Ich muss einen dynamic_cast verwenden - aber RTTI ist nicht verfügbar! Wie konvertiere ich ein Objekt von Eltern zu Kind, wenn es eine virtuelle Vererbung ohne dynamic_cast gibt?

Es sieht wie folgt aus:

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... 

sind meine Fehler sind:

Fehler C2635: kann nicht konvertiert ein 'A *' zu einem 'D *'; Umwandlung von einer virtuellen Basisklasse

impliziert

Fehler C2440: 'initialisieren': von nicht konvertieren 'test_convert :: A *' zu 'test_convert :: D *' Cast von der Basis abgeleitet erfordert dynamic_cast oder static_cast

Irgendwelche Ideen?

+0

heh, gut MS sagt nur, das virtuelle Schlüsselwort aus dem Code zu entfernen, und es wird das Problem lösen. Siehe ihre Dokumentation für den Fehler, ich mache keine Witze. –

+1

Wer zum Teufel hat dir gesagt, Android ist eine Nicht-RTTI-Plattform? Das r5 und neuere NDK sollte RTTI unterstützen (ich glaube, Sie müssen es mit '-frtti' einschalten, aber es sollte dann funktionieren). Auch für ältere Plattformen, da alles statisch verknüpft ist. –

Antwort

12

Sie können diesen Cast nur mit dynamic_cast; Keine andere Besetzung wird dies tun.

Wenn Sie Ihre Interfaces nicht so entwerfen können, dass Sie diese Art von Cast nicht durchführen müssen, können Sie nur die Casting-Funktionalität in Ihre Klassenhierarchie einfügen.

z. (Schrecklich 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

Ich weiß nicht, ob zu minus oder füge für diese Antwort hinzu. –

+2

@Noah: Zu meiner Verteidigung habe ich "schrecklich hacky" gesagt! –

+2

@Noah: Die Hacky-Version sieht besser aus als eine Cast-Matrix, die ich vor langer Zeit bei einem Projekt gesehen habe. Auf Rtti-less-Systemen fanden die Leute nichts Besseres, als jeder Klasse eine eindeutige ID zu geben und die ID als einen Index in eine zweidimensionale Matrix zu verwenden. matrix [cast_what] [cast_to] war eine ID der Klasse, das Ergebnis von dynamic_cast. – Dummy00001

-2

Solange Sie eine andere Art und Weise haben Sie sicher zu machen, was Sie tun, ist zur Laufzeit sicher geben, nur reinterpret_cast verwenden.

Es ist im Grunde die gleiche Sache wie eine C-Stil-Besetzung, so verwenden Sie es nur, wenn Sie müssen, aber es wird ermöglichen, den obigen Code zu kompilieren.

+1

Nun, ich habe versucht, aber ich habe eine Warnmeldung (4946: reinterpret_cast zwischen verwandten Klassen verwendet: 'class1' und 'class2'). Beachten Sie, dass die Warnung standardmäßig deaktiviert ist. Ich glaube, dass die reinterpret_cast nur den Zeiger (wie es sagt) neuinterpretiert, aber es wirft es nicht und es verursacht Probleme, wenn es virtuelle Vererbungen oder mehrere Eltern gibt, seit das Umsetzen auf ein Elternteil den Wert des Zeigers ändern kann. Daher funktioniert die Funktion "reinterpret_cast" nicht in meinem Fall, wenn der von mir verwendete Drittanbieter solche Konzepte (mehrere Eltern, virtuelle Vererbung usw.) in großem Umfang nutzt. – Adam

3

In den meisten Fällen kann das Besuchermuster verwendet werden, um Downcasts zu vermeiden. Es kann auch verwendet werden, um dynamic_cast zu vermeiden.

Einige Einschränkungen:

1) Es muss möglich sein, die beanstandeten Klassen zu ändern.
2) Sie müssen möglicherweise jede abgeleitete Klasse kennen.
3) Die Objekte müssen bekanntermaßen mindestens von der Basisklasse abgeleitet sein. Sie können nicht versuchen, völlig unabhängige Typen zu erzeugen. (Dies scheint erfüllt zu sein: "Ich möchte von der Basisklasse in die abgeleitete Klasse reduzieren")

Im folgenden Beispiel habe ich Vorlagen verwendet. Diese können leicht entfernt werden, erfordern jedoch einiges an Schreibaufwand.

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

Das Problem mit virtueller Vererbung ist, dass die Adresse der Basisklasse nicht unbedingt das gleiche wie die abgeleitete Adresse ist. Also, auch reinterpret_cast oder void* wirft nicht helfen.

Eine Möglichkeit, dies zu lösen, ohne dynamic_cast zu verwenden, besteht darin, den Versatz zwischen beiden Zeigertypen (den genauen Typ und den Ref-Typ) zu berechnen, um die Objektadresse während des Cast entsprechend zu ändern.

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

Randnotiz: Die Deklaration des Offsets wird statisch gemacht, da sie theoretisch für jedes Paar T, E nur einmal berechnet werden muss. In der Praxis hat die Verwendung von 'static' jedoch einen Laufzeitaufwand (als 'if (! Var_initiated) var_ = ...' umgeschrieben). Eine Möglichkeit, um daraus herauszukommen, besteht darin, eine Template-Klasse zu instanziieren, die den Offset-Wert für jedes Paar T, E statisch speichert, aber das ist eine andere Geschichte ... – log0

1

der Code:

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 = ... 

nicht sehr gut als static const E* exact_obj für mich arbeiten Null ist, so statisch const T& exact_obj_ref = *exact_obj derefs Null, auch, und somit static const ptrdiff_t exact_offset wird auch Null.

Es scheint mir, dass die abgeleitete Klasse instanziiert werden muss (was ein Problem für abstrakte Klassen sein kann ...). Also mein Code ist:

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); 
} 

Getestet unter MSVC 2008, WinXP 32b.

Alle Kommentare/bessere Lösung (en) sind willkommen.

LuP

+0

Dies ist problematisch, wenn Ds Konstruktor Nebenwirkungen hat. Vielleicht ist es die bessere Lösung, einen entsprechend großen und ausgerichteten Zeichenpuffer an den D-Zeiger neu zu interpretieren. Um mit C++ Casting Namenskonventionen zu bleiben, wie wäre es mit der Benennung von 'virtual_cast (B *)'? Ich habe es noch nicht gründlich durchdacht, aber gibt es einige Vererbungshierarchien, für die das scheitern wird? Vielleicht eine Klasse, die mehrfach von derselben virtuellen Klasse geerbt hat (wenn auch indirekt) oder vielleicht mit einer Kombination aus nicht-virtueller Vererbung zum Booten. Anyway +1 –

+0

Hallo Thomas, könntest du bitte besser deine Idee beschreiben, den Zeichenpuffer zu D * neu zu interpretieren? – Juster

4

Android tut Unterstützung RTTI. Du benötigst das neueste NDK (mindestens r5, das neueste ist r6) und musst gegen das GNU stdlibC++ anstelle des Standards kompilieren.

Schon vorher gab es den Rebuild von CrystaX, der Ausnahmen und rtti unterstützte (wir mussten das bis zum offiziellen NDK r5c verwenden, da r5a und r5b die Unterstützung hatten, aber auf älteren (vor 2.3) Systemen abstürzten).

PS: Jemand sollte Lieferanten wirklich verbieten zu sagen, dass sie C++ unterstützen, wenn sie keine Ausnahmen und rtti unterstützen, weil die meisten der Standardbibliothek, und das ist Teil des C++ - Standards, ohne auch nicht funktioniert. Plus nicht unterstützt sie ist albern, vor allem für die Ausnahmen, weil Code mit Ausnahmen effizienter ist als einer ohne (vorausgesetzt, sie sind ordnungsgemäß verwendet, um außergewöhnliche Fällen zu signalisieren).