2011-01-03 5 views
1

Ich habe in letzter Zeit meine Kenntnisse über C++ aktualisiert und aktualisiert, und das Erlernen von strengem Aliasing hat mich ein wenig davor gewarnt, Zeiger von einem Typ auf einen anderen zu übertragen. Ich weiß, dass dies folgende Codebeispiel auf meinem Compiler in der Praxis funktioniert, aber ich möchte sicherstellen, dass es den aktuellen Normen und Richtlinien entspricht:Ist es in Ordnung, eine pointer-to-member-Variable in diesem Fall zu erzeugen?

#include <iostream> 

using namespace std; 

class MyBase { 

    public: 
    virtual void DoSomething() = 0; 

}; 

class MyDerived1 : public MyBase { 

    public: 
    virtual void DoSomething() { 
     cout << "I'm #1" << endl; 
    } 

}; 

class MyDerived2 : public MyBase { 

    public: 
    virtual void DoSomething() { 
     cout << "I'm #2" << endl; 
    } 

}; 

template <typename Base, typename Member1, typename Member2> 
struct Tuple { 

    public: 
    Base* Get(int i) { 
     return &(this->*(lookupTable[i])); 
    } 

    private: 
    Member1 member1; 
    Member2 member2; 

    static Base Tuple::* const lookupTable[2]; 

}; 

template <typename Base, typename Member1, typename Member2> 
Base Tuple<Base, Member1, Member2>::* const Tuple<Base, Member1, Member2>::lookupTable[2] = { 
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member1), 
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member2) 
}; 

int main() { 

    Tuple<MyBase, MyDerived1, MyDerived2> tuple; 

    tuple.Get(0)->DoSomething(); 
    tuple.Get(1)->DoSomething(); 

    return 0; 

} 

Im Wesentlichen ist diese einfache Tupel ein Paar Elemente enthält, von denen jeder ableiten sollte aus einer gemeinsamen Basisklasse. Die Get-Funktion gibt eine Base* an das Element zurück, das der angegebene Index darstellt.

Der Schlüsselteil, über den ich mich wundere, ist die reinterpret_casts. Ich weiß, dass Gießen von Derived Struct::* bis Base Struct::* ist im Allgemeinen ein Nein-Nein, aber in diesem Fall verwende ich nur die Zeiger-zu-Mitglied-Variable, um einen Zeiger auf das Objekt zu erhalten. (Ich versuche nicht, ein abgeleitetes Objekt so zu kopieren, als ob es ein Basisobjekt wäre, oder ein Basisobjekt in den Speicher eines abgeleiteten Objekts zu stopfen.) Dies funktioniert auf G ++ wie beabsichtigt, und ich möchte nur sicher sein, dass ich es nicht bin dafür von jedem Compiler kompromittiert werden.

Antwort

1

Verwendung von reinterpret_cast ist fast nie portabel. Hinzu kommt, dass, ist die nur gültig Verwendung von Zeigern auf Member Abgüsse die implizite Umwandlung von Type Derived::* zu Type Base::* und sorgfältigen Verwendungen der static_cast von Type Base::* zu Type Derived::*. Da Sie den Typ des Elements und nicht den Typ des Objekts ändern möchten, das Elemente enthält, ist dies keines von beiden.

Wie wäre es mit kleinen Funktionen in diesem Array anstelle von Zeigern zu Mitgliedern? Der folgende Code wird getestet und sollte vollständig portabel sein.

#include <iostream> 

using namespace std; 

class MyBase { 

    public: 
    virtual void DoSomething() = 0; 

}; 

class MyDerived1 : public MyBase { 

    public: 
    virtual void DoSomething() { 
     cout << "I'm #1" << endl; 
    } 

}; 

class MyDerived2 : public MyBase { 

    public: 
    virtual void DoSomething() { 
     cout << "I'm #2" << endl; 
    } 

}; 

template <typename Base, typename Member1, typename Member2> 
struct Tuple { 

    public: 
    Base* Get(int i) { 
     return &(this->*lookupTable[i])(); 
    } 

    private: 
    Member1 member1; 
    Member2 member2; 

    template <typename MemType, MemType Tuple::*member> 
    Base& GetMember() { return this->*member; } 

    typedef Base& (Tuple::*get_member_func)(); 
    static const get_member_func lookupTable[2]; 

}; 

template <typename Base, typename Member1, typename Member2> 
const typename Tuple<Base, Member1, Member2>::get_member_func 
Tuple<Base, Member1, Member2>::lookupTable[2] = { 
    &Tuple::GetMember<Member1, &Tuple::member1>, 
    &Tuple::GetMember<Member2, &Tuple::member2> 
}; 

int main() { 

    Tuple<MyBase, MyDerived1, MyDerived2> tuple; 

    tuple.Get(0)->DoSomething(); 
    tuple.Get(1)->DoSomething(); 

    return 0; 

} 
+0

Ich hatte auch diesen Gedanken, aber mein Hauptgrund für dieses komplizierte Setup ist es, so wenige Funktionsaufrufe wie möglich zu erreichen. Ich kann dies als letzten Ausweg tun, aber ich hoffe, dass jemand einen (konformen) Weg kennt, dies zu erreichen, ohne einen Funktionsaufruf zu benötigen. – nonoitall

2

Sie sollten reinterpret_cast dort nicht verwenden. Eigentlich sollte man die Verwendung von reinterpret_cast nirgends erwähnen, wo das Ziel Portabilität ist. reinterpret_cast ist per Definition etwas, das plattformspezifische Ergebnisse hat.

Um einen Zeiger auf die Basis in den Zeiger der abgeleiteten Klasse umzuwandeln, verwenden Sie dynamic_cast, es gibt NULL zurück, wenn das Objekt nicht von der abgeleiteten Klasse stammt. Wenn Sie absolut sicher sind, dass die Klasse korrekt ist, können Sie static_cast verwenden.

+1

Das Ergebnis von "reinterpret_cast" ist nicht "per Definition" plattformspezifisch. Es ist auch nicht plattformspezifisch unter einem anderen Namen. Es ist jedoch gefährlich, weil es Typ-Sicherheit wegwirft. Das Ergebnis kann katastrophal sein und die Pflege des Codes prekär. Aber sonst, ja, dynamic_cast ist das, was stattdessen verwendet werden sollte. –

+0

dynamic_cast wird hier nicht funktionieren, da diese Zeiger nicht auf Objekte zeigen - sie zeigen auf relative Mitglied Offsets. An dem Punkt, an dem die Besetzung verwendet wird, gibt es keine RTTI für eine dynamische Besetzung. static_cast funktioniert auch nicht, da die Zeigertypen technisch inkompatibel sind. – nonoitall

+2

Wenn Sie den Standard lesen, dann ist nur der Fall, wenn reinterpret_cast ein definiertes Verhalten ist, zurück in den Typ zu schreiben, von dem aus es vorher reinterpret_cast war. Die übrigen Fälle sind plattformspezifisch. –

0

EDIT: Referenz vom Standard. Wenn ich es richtig lese, da Sie keine der Ausnahmen erfüllen, ist das, was Sie getan haben, unspezifiziert und kann daher bei einem bestimmten Compiler funktionieren oder auch nicht. Es gibt keine Ausnahmen für den Typ des betreffenden Mitglieds.

Von 5.2.10/9 (reinterpret_cast):

rvalue des Typs „Zeiger auf Element von X des Typs T1“ kann zu einem R-Wert vom Typ „Zeiger auf Element umgewandelt explizit sein von Y vom Typ T2 "wenn T1 und T2 beide Funktionstypen sind oder beide Objekttypen.66) Das Nullelement Zeigerwert (4.11) wird in der Nullelementzeigerwert des Zieltyps konvertiert. Das Ergebnis dieser Umwandlung ist nicht spezifiziert, außer in folgenden Fällen:

- Umwandeln eines rvalue des Typs „Zeiger auf Element Funktion“ auf einen anderen Zeiger auf Mitgliedsfunktionstyp und zurück zu seiner Vorlagenart ergibt den ursprünglichen Zeiger auf Elementwert.

- rvalue des Typs „Zeiger auf Daten Mitglied X des Typs T1“ auf den Typ „Zeiger auf Datenelement Y vom Typ T2“ (wo die Ausrichtungsanforderungen von T2 konvertieren nicht strenger als die von T1) und zurück zu seinem ursprünglichen Typ ergibt den ursprünglichen Zeiger auf Mitglied Wert.

+0

Wenn ich MyDerived1 auswählte, musste ich einen Mitgliedszeiger falsch auf MyDerived2 zu MyDerived1 umwandeln. Die Umkehrung gilt auch. – nonoitall

-1

C-Stil ist immer besser als reinterpret_cast.

Wenn C-Style-Cast funktioniert, ist es unabhängig von der Plattform gültig.

Vermeiden Sie immer reinterpret_cast.

EDITED

Ich meine, mit reinterpret_cast Sie falsche Speicheradresse, C Casts wie ABI alle Plattform bezogene Aufgaben bearbeiten zeigen könnte, Speicher align, Zeigergröße usw.

EDITED Durch die Inspiration von Kommentatoren habe ich ISO/IEC 14882: 2003 Abschnitt 5.2.10 "Reinterpret_cast" gelesen.

Natürlich ist mein Verständnis begrenzt, aber es fällt mir ein, mich zu erinnern, warum ich reinterpret_cast in erster Linie hasste.

Ich denke, reinterpret_cast ist Mangel an oder hat sehr begrenzte Bewusstsein der Vererbungshierarchie.

Wenn der Cast-Operand ein Instanzzeiger der Klasse ist, die die Vererbungshierarchie kompliziert hat (wie ATL/COM-Klassen), reicht ein reininterpret_cast aus, um den Prozess mit unverständlichen Fehlern zu beenden.

Wir können C-Style-Besetzung mit vagen Kenntnisse der tatsächlichen Cast-Operation verwenden. Aber wir müssen wirklich genau wissen, um reinterpret_cast sicher zu verwenden.

+0

Wenn das der Fall ist, was ist der Zweck von reinterpret_cast? – nonoitall

+0

Die ursprüngliche Idee war, die Abgüsse zu berücksichtigen, um die Absicht klarer zu machen, da C-Stilabgüsse ein bisschen wie eine Küchenspüle/Schweizer Taschenmesser sind: Man kann nicht einmal den Unterschied zwischen einer Umwandlung und einer Neuinterpretation unterscheiden. Unglücklicherweise können die Umwandlungen im Standard den Platz nicht richtig partitionieren und enthalten mindestens eine absurde Regel, die die Neuinterpretation beschränkt (wenn keine Einschränkung logisch sinnvoll ist). Es ist schade, die Idee war gut. – Yttrill

+0

reinterpret_cast ist das: "Ich bin der Meister! Der Compiler fällt ab! Ich weiß, was ich tue!". Sehr gefährlich für gewöhnliche Menschen wie uns. – 9dan