2015-10-23 15 views
7

Ich habe die folgenden - verwandten - Fragen untersucht, und keiner von ihnen scheint mein genaues Problem zu adressieren: one, two, three.Ist die Verwendung von reinterpret_cast auf unterschiedlich qualifizierten Strukturelementen sicher?

Ich schreibe eine Sammlung von denen die Elemente (Schlüssel-Wert-Paare) zusammen mit einigen Buchhaltungsinformationen gespeichert sind:

struct Element { 
    Key key; 
    Value value; 
    int flags; 
}; 

std::vector<Element> elements; 

(Der Einfachheit halber wird angenommen, dass beide Key und Value Standard-Layout-Typen sind Die Sammlung wird sowieso nicht mit anderen Typen verwendet.)

Um Iterator-basierten Zugriff zu unterstützen, habe ich Iteratoren geschrieben, die operator-> und überschreiben, um dem Benutzer einen Zeiger bzw. eine Referenz zurückzugeben , t o das Schlüssel-Wert-Paar. Aufgrund der Art der Sammlung darf der Benutzer den zurückgegebenen Schlüssel jedoch nie ändern. Aus diesem Grund habe ich eine KeyValuePair Struktur erklärt:

struct KeyValuePair { 
    const Key key; 
    Value value; 
}; 

Und habe ich operator-> auf dem Iterator wie folgt umgesetzt:

struct iterator { 
    size_t index; 

    KeyValuePair *operator->() { 
     return reinterpret_cast<KeyValuePair *>(&elements[index]); 
    } 
}; 

Meine Frage ist: ist diese Verwendung von reinterpret_cast wohldefinierten oder ruft es undefiniertes Verhalten auf? Ich habe versucht, relevante Teile der Norm zu interpretieren und untersucht Antworten auf Fragen zu ähnlichen Problemen, jedoch scheiterte ich von ihnen eine endgültige Schlussfolgerung zu ziehen, weil ...:

  • die beiden Strukturtypen teilen einige erste Datenelemente (nämlich key und value), die sich nur in const -Identifikation unterscheiden;
  • Der Standard besagt nicht explizit, dass T und cv T Layout-kompatibel sind, aber es gibt auch keine Umkehrung; fordert außerdem, dass sie dieselben Repräsentations- und Ausrichtungsanforderungen haben sollten;
  • Zwei Standardlayout-Klassentypen haben eine gemeinsame Anfangssequenz, wenn die ersten, jedoch viele Mitglieder Layout-kompatible Typen haben;
  • für Union-Typen, die Mitglieder des Klassentyps enthalten, die eine gemeinsame Anfangssequenz teilen, ist es erlaubt, die Mitglieder einer solchen Anfangssequenz unter Verwendung eines der Union-Mitglieder zu untersuchen (9.2p18). - Es gibt keine ähnliche explizite Garantie, die über reinterpret_cast ed pointer-to-structs gemacht wird, die eine gemeinsame Anfangssequenz teilen. - es ist jedoch garantiert, dass ein Pointer-to-struct auf sein Anfangselement zeigt (9.2p19).

nur diese Informationen verwenden, fand ich es unmöglich, ob die Element und KeyValuePair structs eine gemeinsame Anfangssequenz teilen abzuleiten, oder irgendetwas anderes gemeinsam haben, dass meine reinterpret_cast rechtfertigen würde.

Nebenbei, wenn Sie denken, mit reinterpret_cast für diesen Zweck ist unangemessen, und ich bin wirklich vor einem XY-Problem und daher sollte ich einfach etwas anderes tun, um mein Ziel zu erreichen, lassen Sie es mich wissen.

+0

Fragen Sie nach einem [Tag: Sprachanwalts] Urteil? Funktioniert es eigentlich? 'reininterpret_cast' ist fast immer der falsche Ansatz, sollte eine' const_cast' nicht gut für das funktionieren, was Sie behaupten? –

+0

@ πάνταῥεῖ Ja, ich bitte um ein Sprach-Anwalt-Urteil. ** Ich kann unmöglich sagen, ob es tatsächlich funktioniert ** oder es scheint nur "zu funktionieren", da ich nicht weiß, ob es undefiniertes Verhalten aufruft. Ich weiß nicht, ob "const_cast" in diesem Fall funktionieren sollte, aber lassen Sie es mich versuchen - es ist jedoch immer noch nicht klar, ob "const_cast" das Kompilieren bedeutet, dass dies gut definiert ist. –

+0

_ "aber, ob const_cast kompilieren würde bedeuten, dass dies gut definiert ist." _ Natürlich denke ich, dass das der Zweck ist. Die Verwendung von 'reininterpret_cast' kann nichts garantieren. –

Antwort

7

Meine Frage ist: Ist diese Verwendung von reinterpret_cast gut definiert, oder ruft es undefiniertes Verhalten?

reinterpret_cast ist der falsche Ansatz hier, Sie verletzen einfach strenge Aliasing. Es ist etwas verwirrend, dass reinterpret_cast und union hier auseinander gehen, aber die Formulierung ist sehr klar über dieses Szenario.

Sie könnten besser einfach sein thusly eine Vereinigung definieren:

union elem_t { 
    Element e{}; KeyValuePair p; 
    /* special member functions defined if necessary */ 
}; 

... und die Verwendung dieser als Vektor-Elementtyp. Beachten Sie, dass cv-Qualifikation wird ignoriert, wenn das Layout-compability Bestimmung - [basic.types]/11:

Zwei Typen cv1T1 und cv2T2 sind Layout-kompatible Typen wenn T1 und T2 sind die gleicher Typ, [...]

Daher Element und KeyValuePair tatsächlich tun, um eine gemeinsame Anfangssequenz teilen und die entsprechenden Mitglieder der p Zugriff bereitgestellt e ist am Leben, ist klar definiert.


Ein anderer Ansatz: Definieren

struct KeyValuePair { 
    Key key; 
    mutable Value value; 
}; 

struct Element : KeyValuePair { 
    int flags; 
}; 

nun einen Iterator zur Verfügung stellen, die einfach ein const_iterator aus dem Vektor Wraps und upcasts die Referenzen/Zeiger ausgesetzt werden. key kann nicht geändert werden, aber value wird sein.

+1

Danke! Es ist eigentlich nicht überraschend zu hören, dass "reinterpret_cast" einfach falsch ist. –

+0

Es ist nichts falsch mit 'struct Element {struct KeyValuePair Paar; int flags} '. – Persixty