Momentan arbeite ich mit einer Legacy-C ++ - Code-Basis. In dieser Codebasis werden Zeiger auf Objekte in void-Zeiger konvertiert und dann in einer C-Bibliothek gespeichert. Betrachten Sie den folgenden Code:
%Vor% Die Objekte interface
und debug_interface
werden auf dem Heap zugeordnet und die Adresse wird in einem void-Zeiger gespeichert. Zu einem bestimmten Zeitpunkt werden die Zeiger abgerufen und dann zurück in die Basisklasse interface
geworfen. Dann wird der virtuelle Funktionsaufruf aufgerufen. Siehe
Zunächst verstehe ich nicht, warum reinterpret_cast verwendet wird. Soweit ich weiß, kann pointer-to-objects implizit in void * umgewandelt werden. Um diesen Cast explizit zu machen, wäre ein static_cast ausreichend, oder? Aber die wichtigere Frage: Ist es wirklich sicher, den Zeiger debug_handle auf Schnittstelle * (nicht auf debug_interface *) zu werfen und den virtuellen Aufruf aufzurufen? Nach dem C ++ - Standard (5.2.10) ist dies ein undefiniertes Verhalten:
Ein Zeiger auf ein Objekt kann explizit in einen Zeiger umgewandelt werden zu einem anderen Objekttyp.69 Wenn ein Prvalue v vom Typ "Zeiger zu T1 "wird in den Typ" Zeiger auf CV T2 "umgewandelt, das Ergebnis ist static_cast (static_cast (v)) wenn sowohl T1 als auch T2 sind Standard-Layout-Typen (3.9) und die Ausrichtungsanforderungen von T2 sind nicht strenger als die von T1. Einen Prvalue des Typs konvertieren "Zeiger auf T1" auf den Typ "Zeiger auf T2" (wobei T1 und T2 sind Objekttypen und wo die Ausrichtung Anforderungen von T2 sind nein strenger als die von T1) und zurück zu seinem ursprünglichen Typ der ursprüngliche Zeigerwert Das Ergebnis eines anderen solchen Zeigers Konvertierung ist nicht spezifiziert.
Die Konvertierung von handle
nach foo1
sollte in Ordnung sein, aber ich kann wieder einen statischen_cast verwenden?
Bearbeiten Mein Beispiel-Quellcode war falsch. debug_interface ist eine abgeleitete Klasse von Schnittstellen.
Haftungsausschluss: Dieser erste Teil wurde geschrieben, als die beiden Schnittstellen nicht durch Vererbung in Beziehung standen.
Das undefinierte Verhalten passiert tatsächlich hier:
%Vor% Hier verwenden Sie die interface
API für einen Zeiger auf ein Objekt, das diese API implementiert, nicht .
Die Tatsache, dass sowohl interface
als auch debug_interface
das Element foo()
als erste Methode implementieren, ändert nichts. Diese Klassen sind nicht durch Vererbung verknüpft, daher sind sie nicht kompatibel.
Der Auszug, den Sie zitieren, behandelt Fälle, in denen die -Konvertierung selbst zulässig ist. In meinem Fall ist mein Verständnis, dass Sie tatsächlich einen Zeiger auf debug_interface
in einen Zeiger auf interface
konvertieren können: Das einzige sichere Ding, das Sie jetzt mit Ihrem Zeiger auf die Schnittstelle machen können, ist, es in ein% co_de umzuwandeln % Zeiger: Verwenden Sie es, um auf debug_interface
Mitglieder zuzugreifen, ist unsicher.
BEARBEITEN: Wenn interface
öffentlich von debug_interface
abgeleitet wird, ist das ein anderes Problem.
In diesem Fall wäre es absolut sicher, von interface
nach debug_interface*
zu konvertieren: Die abgeleitete zu base Konvertierung kann sogar implizit vom Compiler übernommen werden.
Um jedoch sicher zu sein, muss diese Besetzung direkt gemacht werden, entweder durch:
interface*
static_cast
(was zu einer Laufzeitüberprüfung führen würde, bei der es sich um einen Upcast handelt). Es ist ein undefiniertes Verhalten, es durch zwei dynamic_cast
zu tun: es wird wahrscheinlich für die einzelne Vererbung (auf einigen Compilern) funktionieren, aber es wird vom Standard absolut nicht garantiert.
Es wäre auch ein undefiniertes Verhalten, wenn du es durch zwei reinterpret_cast
tust. Der Standard garantiert das (Hervorhebung von mir):
Ein Wert vom Typ Zeiger auf ein Objekt, das in "Zeiger auf cv void" und zurück in den Originalzeiger Typ konvertiert wurde, hat seinen ursprünglichen Wert.
In Ihrem Beispiel konvertieren Sie nicht zurück zum ursprünglichen Zeiger, sondern zu einem anderen Zeigertyp: Der Standard gibt Ihnen keine Garantie für den Wert, den Sie erhalten.
Wissen, dass:
static_cast
in debug_interface
zu konvertieren
interface
und dann zurück in den Zeiger zum selben Objekttyp zu konvertieren. Sie können das zusammenstellen, um eine standardmäßige garantierte Lösung zu erhalten:
%Vor% Sie haben Recht, es ist undefiniert. Wenn Sie void*
zurückwerfen, sollten Sie immer den ursprünglichen Zeigertyp verwenden. Und Sie sollten static_cast
anstelle von dynamic_cast
verwenden.
So könnte Ihr Code sicher geschrieben werden als:
%Vor%Und in der Tat, wenn Sie eine Klasse haben wie:
%Vor%Und dann schreibe:
%Vor%Sie werden sehen, warum die UB.
Dies ist vom Standard nicht erlaubt. Der richtige Weg zum Speichern des Zeigers wäre:
%Vor%Sie können das natürlich in einer Zeile tun:
%Vor%aber das ist zu unhandlich und fehleranfällig für meinen Geschmack.
Der Standard ermöglicht es Ihnen, einen Zeiger auf void*
und zurück zu werfen, aber Sie müssen ihn auf genau den gleichen Zeigertyp zurückwerfen, mit dem Sie begonnen haben. Der Zeiger auf eine Basisklasse ist kein Ersatz.
Ihr Code hat eine gute Chance, abzustürzen und zu brennen, wenn Sie debug_interface
mehrfache oder virtuelle Vererbung verwenden, aber selbst bei einfachen Einsprüchen ist es nicht konform.
Tags und Links c++ reinterpret-cast virtual void static-cast