Virtuelle Funktionen und Umwandlung in void und zurück

8

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

%Vor%

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.

    
IcePic 21.08.2015, 07:30
quelle

4 Antworten

4

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:

  • a interface*
  • a 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.

Mögliche Lösung

Wissen, dass:

  1. Es ist sicher, direkt von static_cast in debug_interface zu konvertieren
  2. Es ist sicher, von einem Zeiger zum Objekttyp in 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%     
Ad N 21.08.2015 08:31
quelle
1

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.

    
rodrigo 21.08.2015 08:50
quelle
1

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.

    
n.m. 21.08.2015 08:56
quelle
0

Sie haben Recht, und dieser Code bricht normalerweise für die zweite Schnittstelle einer Klasse.

    
MSalters 21.08.2015 08:07
quelle