Wann sollten static_cast, dynamic_cast, const_cast und reinterpret_cast verwendet werden?

2493

Was sind die richtigen Verwendungen von:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Besetzung im C-Stil (type)value
  • Besetzung im Funktionsstil type(value)

Wie entscheidet man, welche in welchen konkreten Fällen verwendet werden soll?

James
quelle
3
Einige nützliche konkrete Beispiele für die Verwendung verschiedener Arten von Besetzungen finden Sie in der ersten Antwort auf eine ähnliche Frage in diesem anderen Thema .
TeaMonkie
2
Sie können oben wirklich gute Antworten auf Ihre Frage finden. Aber ich möchte hier noch einen Punkt anführen: @ e.James "Es gibt nichts, was diese neuen C ++ - Cast-Operatoren können und was C-Style-Cast nicht kann. Diese werden mehr oder weniger hinzugefügt, um die Lesbarkeit des Codes zu verbessern."
BreakBadSP
@BreakBadSP Die neuen Casts dienen nicht nur der besseren Lesbarkeit des Codes. Sie sind da, um es schwieriger zu machen, gefährliche Dinge zu tun, wie Konstante wegzuwerfen oder Zeiger anstelle ihrer Werte zu werfen. static_cast hat viel weniger Möglichkeiten, etwas Gefährliches zu tun als eine Besetzung im AC-Stil!
Zweiundvierzig
@ FourtyTwo stimmte zu
BreakBadSP

Antworten:

2570

static_castist die erste Besetzung, die Sie verwenden sollten. Es führt beispielsweise implizite Konvertierungen zwischen Typen aus (z. B. intto floatoder Zeiger auf void*) und kann auch explizite Konvertierungsfunktionen (oder implizite Konvertierungsfunktionen) aufrufen. In vielen Fällen ist eine explizite Angabe static_castnicht erforderlich, es ist jedoch wichtig zu beachten, dass die T(something)Syntax der Syntax entspricht (T)somethingund vermieden werden sollte (dazu später mehr). A T(something, something_else)ist jedoch sicher und ruft garantiert den Konstruktor auf.

static_castkann auch durch Vererbungshierarchien umgewandelt werden. Es ist nicht erforderlich, wenn nach oben (in Richtung einer Basisklasse) gewirkt wird, aber wenn nach unten gewirkt wird, kann es verwendet werden, solange es nicht durch virtualVererbung gewirkt wird. Es wird jedoch keine Überprüfung durchgeführt, und es ist ein undefiniertes Verhalten, static_casteine Hierarchie auf einen Typ herunterzufahren, der eigentlich nicht der Typ des Objekts ist.


const_castkann zum Entfernen oder Hinzufügen consteiner Variablen verwendet werden; Kein anderer C ++ - Cast kann ihn entfernen (nicht einmal reinterpret_cast). Es ist wichtig zu beachten, dass das Ändern eines früheren constWerts nur dann undefiniert ist, wenn die ursprüngliche Variable lautet const. Wenn Sie damit consteinen Verweis auf etwas entfernen, mit dem nicht deklariert wurde const, ist es sicher. Dies kann beispielsweise beim Überladen von Elementfunktionen hilfreich sein const. Es kann auch zum Hinzufügen constzu einem Objekt verwendet werden, z. B. zum Aufrufen einer Elementfunktionsüberladung.

const_castfunktioniert auch ähnlich volatile, obwohl das weniger verbreitet ist.


dynamic_castwird ausschließlich zur Behandlung von Polymorphismus verwendet. Sie können einen Zeiger oder Verweis auf einen beliebigen polymorphen Typ in einen anderen Klassentyp umwandeln (ein polymorpher Typ verfügt über mindestens eine deklarierte oder geerbte virtuelle Funktion). Sie können es für mehr als nur das Abwärtswerfen verwenden - Sie können es seitwärts oder sogar eine andere Kette hochwerfen. Der dynamic_castsucht das gewünschte Objekt und gibt es wenn möglich zurück. Wenn dies nicht möglich ist, wird es nullptrim Fall eines Zeigers zurückgegeben oder std::bad_castim Fall einer Referenz geworfen .

dynamic_casthat jedoch einige Einschränkungen. Es funktioniert nicht, wenn die Vererbungshierarchie mehrere Objekte desselben Typs enthält (der sogenannte "gefürchtete Diamant") und Sie keine virtualVererbung verwenden. Es kann auch nur eine öffentliche Vererbung durchlaufen - es wird immer nicht durchlaufen protectedoder privatevererbt. Dies ist jedoch selten ein Problem, da solche Formen der Vererbung selten sind.


reinterpret_castist die gefährlichste Besetzung und sollte sehr sparsam eingesetzt werden. Es verwandelt einen Typ direkt in einen anderen - z. B. das Umwandeln des Werts von einem Zeiger auf einen anderen oder das Speichern eines Zeigers in einem intoder allen möglichen anderen bösen Dingen. Die einzige Garantie, die Sie erhalten, reinterpret_castist, dass Sie normalerweise genau den gleichen Wert erhalten , wenn Sie das Ergebnis auf den ursprünglichen Typ zurücksetzen (jedoch nicht, wenn der Zwischentyp kleiner als der ursprüngliche Typ ist). Es gibt eine Reihe von Konvertierungen, reinterpret_castdie ebenfalls nicht möglich sind. Es wird hauptsächlich für besonders seltsame Konvertierungen und Bitmanipulationen verwendet, z. B. zum Umwandeln eines Rohdatenstroms in tatsächliche Daten oder zum Speichern von Daten in den niedrigen Bits eines Zeigers auf ausgerichtete Daten.


Cast im C-Stil und Cast im Funktionsstil sind Casts mit (type)objectoder type(object)bzw. sind funktional äquivalent. Sie werden als die ersten der folgenden definiert, die erfolgreich sind:

  • const_cast
  • static_cast (obwohl Zugriffsbeschränkungen ignoriert werden)
  • static_cast (siehe oben) also const_cast
  • reinterpret_cast
  • reinterpret_cast, dann const_cast

Es kann daher in einigen Fällen als Ersatz für andere Casts verwendet werden, kann jedoch aufgrund der Fähigkeit, sich in a zu verwandeln, äußerst gefährlich sein. reinterpret_castLetzteres sollte bevorzugt werden, wenn explizites Casting erforderlich ist, es sei denn, Sie sind sicher static_cast, dass es erfolgreich sein wird oder reinterpret_castfehlschlagen wird . Berücksichtigen Sie auch dann die längere, explizitere Option.

Casts im C-Stil ignorieren auch die Zugriffskontrolle, wenn sie a ausführen static_cast. Dies bedeutet, dass sie eine Operation ausführen können, die kein anderer Cast ausführen kann. Dies ist jedoch meistens ein Kludge, und meiner Meinung nach ist dies nur ein weiterer Grund, Casts im C-Stil zu vermeiden.

coppro
quelle
17
dynamic_cast ist nur für polymorphe Typen. Sie müssen es nur verwenden, wenn Sie in eine abgeleitete Klasse umwandeln. static_cast ist sicherlich die erste Option, es sei denn, Sie benötigen speziell die Funktionalität von dynamic_cast. Es ist im Allgemeinen keine wundersame Silberkugel "Typ-Checking Cast".
Jalf
2
Gute Antwort! Eine kurze Bemerkung: static_cast kann erforderlich sein, um die Hierarchie aufzurufen, falls Sie ein abgeleitetes * & haben, um es in Base * & umzuwandeln, da Doppelzeiger / Referenzen die Hierarchie nicht automatisch auflösen. Ich bin vor zwei Minuten auf eine solche (ehrlich gesagt nicht übliche) Situation gestoßen. ;-)
Bartgol
5
* "Keine andere C ++ - Besetzung kann const(nicht einmal reinterpret_cast) entfernen " ... wirklich? Was ist mit reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
user541686
29
Ich denke, ein wichtiges Detail, das oben fehlt, ist, dass dynamic_cast im Vergleich zu static oder reinterpret_cast einen Laufzeitleistungsverlust aufweist. Dies ist wichtig, z. B. in Echtzeitsoftware.
jfritz42
5
Erwähnenswert reinterpret_castist möglicherweise, dass dies häufig die Waffe der Wahl ist, wenn es um die undurchsichtigen Datentypen einer API geht
Class Skeleton
333

Verwendung dynamic_castzum Konvertieren von Zeigern / Referenzen innerhalb einer Vererbungshierarchie.

Verwendung static_castfür normale Typkonvertierungen.

Verwendung reinterpret_castfür die Neuinterpretation von Bitmustern auf niedriger Ebene. Mit äußerster Vorsicht verwenden.

Verwenden Sie const_castzum Gießen entfernt const/volatile. Vermeiden Sie dies, es sei denn, Sie verwenden eine const-falsche API.

Fred Larson
quelle
2
Seien Sie vorsichtig mit dynamic_cast. Es basiert auf RTTI und dies funktioniert nicht wie erwartet über die Grenzen gemeinsam genutzter Bibliotheken hinweg. Nur weil Sie ausführbare und gemeinsam genutzte Bibliotheken unabhängig voneinander erstellen, gibt es keine standardisierte Möglichkeit, RTTI über verschiedene Builds hinweg zu synchronisieren. Aus diesem Grund gibt es in der Qt-Bibliothek qobject_cast <>, das die QObject-Typinformationen zum Überprüfen von Typen verwendet.
user3150128
198

(Viele theoretische und konzeptionelle Erklärungen wurden oben gegeben)

Im Folgenden sind einige praktische Beispiele aufgeführt, als ich static_cast , dynamic_cast , const_cast , reinterpret_cast verwendet habe .

(Verweist auch darauf, um die Erklärung zu verstehen: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

dynamic_cast:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast:

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
Sumit Arora
quelle
31
Die Theorie einiger anderer Antworten ist gut, aber immer noch verwirrend. Wenn Sie diese Beispiele nach dem Lesen der anderen Antworten sehen, sind sie alle wirklich sinnvoll. Ohne die Beispiele war ich mir immer noch nicht sicher, aber mit ihnen bin ich mir jetzt sicher, was die anderen Antworten bedeuten.
Solx
1
Über die letzte Verwendung von reinterpret_cast: Ist das nicht dasselbe wie die Verwendung static_cast<char*>(&val)?
Lorenzo Belli
3
@ LorenzoBelli Natürlich nicht. Hast Du es versucht? Letzteres ist in C ++ nicht gültig und blockiert die Kompilierung. static_castFunktioniert nur zwischen Typen mit definierten Konvertierungen, sichtbarer Beziehung durch Vererbung oder zu / von void *. Für alles andere gibt es andere Besetzungen. reinterpret castJeder char *Typ darf das Lesen der Darstellung eines Objekts ermöglichen - und einer der wenigen Fälle, in denen dieses Schlüsselwort nützlich ist, und kein zügelloser Generator für implementierungs- / undefiniertes Verhalten. Dies wird jedoch nicht als "normale" Konvertierung angesehen und wird daher von den (normalerweise) sehr konservativen Personen nicht zugelassen static_cast.
underscore_d
2
reinterpret_cast ist ziemlich häufig, wenn Sie mit Systemsoftware wie Datenbanken arbeiten. In den meisten Fällen schreiben Sie Ihren eigenen Seitenmanager, der keine Ahnung hat, welcher Datentyp auf der Seite gespeichert ist, und nur einen ungültigen Zeiger zurückgibt. Es liegt an den höheren Ebenen, eine Besetzung neu zu interpretieren und daraus zu schließen, was sie wollen.
Sohaib
1
Das Beispiel const_cast zeigt undefiniertes Verhalten. Eine als const deklarierte Variable kann nicht dekonstiziert werden. Eine als Nicht-Konstante deklarierte Variable, die an eine Funktion übergeben wird, die eine Konstantenreferenz verwendet, kann in dieser Funktion dekonstituiert werden, ohne dass sie UB ist.
Johann Gerell
99

Es könnte hilfreich sein, wenn Sie ein wenig über Interna wissen ...

static_cast

  • Der C ++ - Compiler weiß bereits, wie man zwischen Skalertypen wie float nach int konvertiert. Verwenden Sie static_castfür sie.
  • Wenn Sie Compiler zu konvertieren von Typ fragen Azu B, static_castAnrufe B‚s vorbei Konstruktor Aals param. Alternativ Akönnte ein Konvertierungsoperator (dh A::operator B()) vorhanden sein. Wenn Sie Bkeinen solchen Konstruktor oder Akeinen Konvertierungsoperator haben, wird ein Fehler bei der Kompilierung angezeigt.
  • Die Umwandlung von A*bis ist B*immer erfolgreich, wenn A und B in der Vererbungshierarchie (oder ungültig) sind. Andernfalls wird ein Kompilierungsfehler angezeigt.
  • Gotcha : Wenn Sie werfen Basiszeiger auf abgeleitete Zeiger aber wenn tatsächliche Objekt Typ nicht wirklich abgeleitet dann Sie nicht bekommen Fehler. Sie erhalten einen schlechten Zeiger und sehr wahrscheinlich einen Segfault zur Laufzeit. Das Gleiche gilt für A&zu B&.
  • Gotcha : Cast von Derived to Base oder umgekehrt erstellt eine neue Kopie! Für Leute, die aus C # / Java kommen, kann dies eine große Überraschung sein, da das Ergebnis im Grunde ein abgeschnittenes Objekt ist, das aus Derived erstellt wurde.

dynamic_cast

  • dynamic_cast verwendet Laufzeittypinformationen, um herauszufinden, ob die Umwandlung gültig ist. Zum Beispiel kann (Base*)to (Derived*)fehlschlagen, wenn der Zeiger nicht vom abgeleiteten Typ ist.
  • Dies bedeutet, dass dynamic_cast im Vergleich zu static_cast sehr teuer ist!
  • Wenn A*für B*cast ungültig ist, gibt dynamic_cast nullptr zurück.
  • Wenn A&to B&ungültig ist, löst dynamic_cast eine bad_cast-Ausnahme aus.
  • Im Gegensatz zu anderen Casts entsteht ein Laufzeit-Overhead.

const_cast

  • Während static_cast nicht-const zu const ausführen kann, kann es nicht anders herum gehen. Der const_cast kann beides tun.
  • Ein Beispiel, bei dem dies nützlich ist, ist das Durchlaufen eines Containers, set<T>der nur seine Elemente als const zurückgibt, um sicherzustellen, dass Sie seinen Schlüssel nicht ändern. Wenn Sie jedoch beabsichtigen, die Nicht-Schlüsselelemente des Objekts zu ändern, sollte dies in Ordnung sein. Sie können const_cast verwenden, um constness zu entfernen.
  • Ein weiteres Beispiel ist, wenn Sie ebenso implementieren möchten T& SomeClass::foo()wie const T& SomeClass::foo() const. Um Codeduplizierungen zu vermeiden, können Sie const_cast anwenden, um den Wert einer Funktion von einer anderen zurückzugeben.

reinterpret_cast

  • Dies besagt im Grunde, dass diese Bytes an diesem Speicherort genommen und als gegebenes Objekt betrachtet werden.
  • Sie können beispielsweise 4 Byte Float auf 4 Byte Int laden, um zu sehen, wie Bits in Float aussehen.
  • Wenn die Daten für den Typ nicht korrekt sind, kann es natürlich zu Segfault kommen.
  • Für diese Besetzung gibt es keinen Laufzeitaufwand.
Shital Shah
quelle
Ich habe die Informationen zum Konvertierungsoperator hinzugefügt, aber es gibt noch einige andere Dinge, die ebenfalls behoben werden sollten, und ich finde es nicht so angenehm, dies zu sehr zu aktualisieren. Elemente sind: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.Sie erhalten UB, was zur Laufzeit zu einem Segfault führen kann, wenn Sie Glück haben. 2. Dynamische Abgüsse können auch beim Cross Casting verwendet werden. 3. Const Casts können in einigen Fällen zu UB führen. Die Verwendung ist mutablemöglicherweise die bessere Wahl, um logische Konstanz zu implementieren.
Adrian
1
@Adrian du bist in jeder Hinsicht korrekt. Die Antwort ist für Leute geschrieben, die mehr oder weniger Anfänger sind, und ich wollte sie nicht mit allen anderen Komplikationen überwältigen, die damit verbunden sind mutable, Cross Casting usw.
Shital Shah
16

Ist dies Ihre Frage beantwortet?

Ich habe es noch nie benutzt reinterpret_castund frage mich, ob es nicht nach schlechtem Design riecht, wenn ich auf ein Gehäuse stoße, das es braucht. In der Codebasis, an der ich arbeite, dynamic_castwird viel verwendet. Der Unterschied static_castbesteht darin, dass eine dynamic_castLaufzeitprüfung durchgeführt wird, die (sicherer) oder nicht (mehr Overhead) sein kann (siehe msdn ).

andreas buykx
quelle
3
Ich habe reintrepret_cast für einen Zweck verwendet - das Herausholen der Bits aus einem Double (dieselbe Größe wie lang auf meiner Plattform).
Joshua
2
reinterpret_cast wird zB für die Arbeit mit COM-Objekten benötigt. CoCreateInstance () hat einen Ausgabeparameter vom Typ void ** (den letzten Parameter), in dem Sie Ihren als "INetFwPolicy2 * pNetFwPolicy2" deklarierten Zeiger übergeben. Dazu müssen Sie so etwas wie reinterpret_cast <void **> (& pNetFwPolicy2) schreiben.
Serge Rogatch
1
Vielleicht gibt es einen anderen Ansatz, aber ich reinterpret_castextrahiere Daten aus einem Array. Zum Beispiel, wenn ich char*einen großen Puffer voller gepackter Binärdaten habe, den ich durchlaufen muss, um einzelne Grundelemente unterschiedlichen Typs zu erhalten. So etwas wie das:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
James Matta
Ich habe es nie benutzt reinterpret_cast, es gibt nicht sehr viele Verwendungszwecke dafür.
Pika der Zauberer der Wale
Persönlich habe ich nur reinterpret_castaus einem Grund verwendet gesehen. Ich habe gesehen, dass Rohobjektdaten, die in einem "Blob" -Datentyp in einer Datenbank gespeichert sind, reinterpret_castverwendet werden, um diese Rohdaten in das Objekt umzuwandeln, wenn die Daten aus der Datenbank abgerufen werden.
ImaginaryHuman072889
15

Zusätzlich zu den anderen bisherigen Antworten gibt es hier ein nicht offensichtliches Beispiel, bei dem dies static_castnicht ausreicht, reinterpret_castum benötigt zu werden. Angenommen, es gibt eine Funktion, die in einem Ausgabeparameter Zeiger auf Objekte verschiedener Klassen zurückgibt (die keine gemeinsame Basisklasse haben). Ein reales Beispiel für eine solche Funktion ist CoCreateInstance()(siehe den letzten Parameter, der tatsächlich ist void**). Angenommen, Sie fordern von dieser Funktion eine bestimmte Objektklasse an, damit Sie den Typ für den Zeiger im Voraus kennen (was Sie häufig für COM-Objekte tun). In diesem Fall können Sie keinen Zeiger auf Ihren Zeiger in void**mit static_cast: Sie benötigen reinterpret_cast<void**>(&yourPointer).

In Code:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

Funktioniert jedoch static_castfür einfache Zeiger (keine Zeiger auf Zeiger), sodass der obige Code neu geschrieben werden kann, um reinterpret_cast(zu einem Preis einer zusätzlichen Variablen) Folgendes zu vermeiden :

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
Serge Rogatch
quelle
Würde es nicht so funktionieren wie &static_cast<void*>(pNetFwPolicy2)statt static_cast<void**>(&pNetFwPolicy2)?
jp48
9

Während andere Antworten alle Unterschiede zwischen C ++ - Casts gut beschrieben haben, möchte ich einen kurzen Hinweis hinzufügen, warum Sie keine C-Casts (Type) varund verwenden sollten Type(var).

Für C ++ - Anfänger scheinen C-ähnliche Casts die Superset-Operation gegenüber C ++ - Casts zu sein (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()), und jemand könnte sie den C ++ - Casts vorziehen . Tatsächlich ist die Besetzung im C-Stil die Obermenge und kürzer zu schreiben.

Das Hauptproblem von Casts im C-Stil besteht darin, dass sie die wahre Absicht des Entwicklers verbergen. Die Casts im C-Stil können praktisch alle Arten von Castings ausführen, von normalerweise sicheren Casts mit static_cast <> () und dynamic_cast <> () bis zu potenziell gefährlichen Casts wie const_cast <> (), bei denen der const-Modifikator entfernt werden kann, damit die const-Variablen entfernt werden kann geändert und neu interpretiert werden_cast <> (), wodurch sogar ganzzahlige Werte in Zeiger neu interpretiert werden können.

Hier ist das Beispiel.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

Der Hauptgrund, warum der Sprache C ++ - Casts hinzugefügt wurden, bestand darin, einem Entwickler zu ermöglichen, seine Absichten zu klären - warum er diese Casting durchführen wird. Durch die Verwendung von C-Casts, die in C ++ perfekt gültig sind, wird Ihr Code weniger lesbar und fehleranfälliger, insbesondere für andere Entwickler, die Ihren Code nicht erstellt haben. Um Ihren Code lesbarer und expliziter zu machen, sollten Sie immer C ++ - Casts C-Casts vorziehen.

Hier ist ein kurzes Zitat aus Bjarne Stroustrups (dem Autor von C ++) Buch The C ++ Programming Language 4th Edition - Seite 302.

Diese Besetzung im C-Stil ist weitaus gefährlicher als die genannten Konvertierungsoperatoren, da die Notation in einem großen Programm schwerer zu erkennen ist und die vom Programmierer beabsichtigte Art der Konvertierung nicht explizit ist.

Timmy_A
quelle
5

Um dies zu verstehen, betrachten wir das folgende Code-Snippet:

struct Foo{};
struct Bar{};

int main(int argc, char** argv)
{
    Foo* f = new Foo;

    Bar* b1 = f;                              // (1)
    Bar* b2 = static_cast<Bar*>(f);           // (2)
    Bar* b3 = dynamic_cast<Bar*>(f);          // (3)
    Bar* b4 = reinterpret_cast<Bar*>(f);      // (4)
    Bar* b5 = const_cast<Bar*>(f);            // (5)

    return 0;
}

Nur Zeile (4) wird fehlerfrei kompiliert. Nur reinterpret_cast kann verwendet werden, um einen Zeiger auf ein Objekt in einen Zeiger auf einen nicht verwandten Objekttyp zu konvertieren.

Dies ist zu beachten: Der dynamic_cast schlägt zur Laufzeit fehl. Bei den meisten Compilern kann er jedoch auch nicht kompiliert werden, da die Struktur des gecasteten Zeigers keine virtuellen Funktionen enthält. Dies bedeutet, dass dynamic_cast nur mit polymorphen Klassenzeigern funktioniert .

Wenn C ++ Guss zu verwenden :

  • Verwenden Sie static_cast als Äquivalent zu einer Umwandlung im C-Stil, die eine Wertkonvertierung durchführt, oder wenn wir einen Zeiger explizit von einer Klasse in ihre Oberklasse hochwandeln müssen.
  • Verwenden Sie const_cast , um das const-Qualifikationsmerkmal zu entfernen.
  • Verwenden Sie reinterpret_cast , um unsichere Konvertierungen von Zeigertypen in und von ganzzahligen und anderen Zeigertypen durchzuführen. Verwenden Sie diese Option nur, wenn wir wissen, was wir tun, und die Aliasing-Probleme verstehen.
Pankaj Kumar Thapa
quelle
2

static_castvs dynamic_castvs reinterpret_castinternals Ansicht auf einen Downcast / Upcast

In dieser Antwort möchte ich diese drei Mechanismen anhand eines konkreten Upcast / Downcast-Beispiels vergleichen und analysieren, was mit den zugrunde liegenden Zeigern / Speicher / Assembly geschieht, um ein konkretes Verständnis für deren Vergleich zu erhalten.

Ich glaube, dass dies eine gute Vorstellung davon geben wird, wie unterschiedlich diese Darsteller sind:

  • static_cast: Versetzt eine Adresse zur Laufzeit (geringe Auswirkungen auf die Laufzeit) und überprüft nicht, ob ein Downcast korrekt ist.

  • dyanamic_cast: macht zur Laufzeit den gleichen Adressoffset wie static_cast, aber auch und eine teure Sicherheitsüberprüfung, ob ein Downcast mit RTTI korrekt ist.

    Mit dieser Sicherheitsüberprüfung können Sie abfragen, ob ein Basisklassenzeiger zur Laufzeit von einem bestimmten Typ ist, indem Sie überprüfen, ob eine Rückgabe nullptreinen ungültigen Downcast anzeigt.

    Wenn Ihr Code nicht in der Lage ist, dies zu überprüfen nullptrund eine gültige Nicht-Abbruch-Aktion auszuführen , sollten Sie daher nur die static_castdynamische Umwandlung verwenden.

    Wenn ein Abbruch die einzige Aktion ist, die Ihr Code ausführen kann, möchten Sie möglicherweise nur die dynamic_castIn-Debug-Builds ( -NDEBUG) aktivieren und static_castanderweitig verwenden, z. B. wie hier ausgeführt , um Ihre schnellen Läufe nicht zu verlangsamen.

  • reinterpret_cast: macht zur Laufzeit nichts, nicht einmal den Adressoffset. Der Zeiger muss genau auf den richtigen Typ zeigen, nicht einmal eine Basisklasse funktioniert. Sie möchten dies im Allgemeinen nur, wenn Rohbyte-Streams beteiligt sind.

Betrachten Sie das folgende Codebeispiel:

main.cpp

#include <iostream>

struct B1 {
    B1(int int_in_b1) : int_in_b1(int_in_b1) {}
    virtual ~B1() {}
    void f0() {}
    virtual int f1() { return 1; }
    int int_in_b1;
};

struct B2 {
    B2(int int_in_b2) : int_in_b2(int_in_b2) {}
    virtual ~B2() {}
    virtual int f2() { return 2; }
    int int_in_b2;
};

struct D : public B1, public B2 {
    D(int int_in_b1, int int_in_b2, int int_in_d)
        : B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
    void d() {}
    int f2() { return 3; }
    int int_in_d;
};

int main() {
    B2 *b2s[2];
    B2 b2{11};
    D *dp;
    D d{1, 2, 3};

    // The memory layout must support the virtual method call use case.
    b2s[0] = &b2;
    // An upcast is an implicit static_cast<>().
    b2s[1] = &d;
    std::cout << "&d           " << &d           << std::endl;
    std::cout << "b2s[0]       " << b2s[0]       << std::endl;
    std::cout << "b2s[1]       " << b2s[1]       << std::endl;
    std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
    std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;

    // Now for some downcasts.

    // Cannot be done implicitly
    // error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
    // dp = (b2s[0]);

    // Undefined behaviour to an unrelated memory address because this is a B2, not D.
    dp = static_cast<D*>(b2s[0]);
    std::cout << "static_cast<D*>(b2s[0])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[0])->int_in_d  " << dp->int_in_d << std::endl;

    // OK
    dp = static_cast<D*>(b2s[1]);
    std::cout << "static_cast<D*>(b2s[1])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[1])->int_in_d  " << dp->int_in_d << std::endl;

    // Segfault because dp is nullptr.
    dp = dynamic_cast<D*>(b2s[0]);
    std::cout << "dynamic_cast<D*>(b2s[0])           " << dp           << std::endl;
    //std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;

    // OK
    dp = dynamic_cast<D*>(b2s[1]);
    std::cout << "dynamic_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;

    // Undefined behaviour to an unrelated memory address because this
    // did not calculate the offset to get from B2* to D*.
    dp = reinterpret_cast<D*>(b2s[1]);
    std::cout << "reinterpret_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}

Kompilieren, ausführen und zerlegen mit:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out

Hier setarchwird ASLR deaktiviert , um den Vergleich von Läufen zu vereinfachen.

Mögliche Ausgabe:

&d           0x7fffffffc930
b2s[0]       0x7fffffffc920
b2s[1]       0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0])            0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d  1
static_cast<D*>(b2s[1])            0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d  3
dynamic_cast<D*>(b2s[0])           0
dynamic_cast<D*>(b2s[1])           0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1])           0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767

Wie unter https://en.wikipedia.org/wiki/Virtual_method_table erwähnt , muss die Speicherdatenstruktur von Dungefähr so ​​aussehen:

B1:
  +0: pointer to virtual method table of B1
  +4: value of int_in_b1

B2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

D:
  +0: pointer to virtual method table of D (for B1)
  +4: value of int_in_b1
  +8: pointer to virtual method table of D (for B2)
 +12: value of int_in_b2
 +16: value of int_in_d

Die Schlüsselfaktor ist, dass die Speicherdatenstruktur von Deine Speicherstruktur enthält, die mit der von B1und der von B2intern kompatibel ist .

Daher kommen wir zu dem kritischen Schluss:

Ein Upcast oder Downcast muss nur den Zeigerwert um einen Wert verschieben, der zur Kompilierungszeit bekannt ist

Auf diese Weise Dberechnet die Typumwandlung beim Übergeben an das Basistyp-Array tatsächlich diesen Versatz und zeigt auf etwas, das genau wie eine B2im Speicher gültige aussieht :

b2s[1] = &d;

mit der Ausnahme, dass dieser die vtable für Danstelle von B2hat und daher alle virtuellen Aufrufe transparent funktionieren.

Jetzt können wir endlich zum Typguss und zur Analyse unseres konkreten Beispiels zurückkehren.

Aus der Standardausgabe sehen wir:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

Daher hat das dort durchgeführte Implizit static_castden Versatz von der vollständigen DDatenstruktur bei 0x7fffffffc930 zu der B2gleichen bei 0x7fffffffc940 korrekt berechnet . Wir schließen auch, dass zwischen 0x7fffffffc930 und 0x7fffffffc940 wahrscheinlich die B1Daten und die vtable liegen.

In den niedergeschlagenen Abschnitten ist es jetzt leicht zu verstehen, wie die ungültigen fehlschlagen und warum:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910: Der Compiler ist gerade zur Kompilierungszeit um 0x10 gestiegen, um zu versuchen, von a B2in das enthaltene zu wechselnD

    Aber weil b2s[0]es kein war D, zeigt es jetzt auf einen undefinierten Speicherbereich.

    Die Demontage ist:

    49          dp = static_cast<D*>(b2s[0]);
       0x0000000000000fc8 <+414>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fcc <+418>:   48 85 c0        test   %rax,%rax
       0x0000000000000fcf <+421>:   74 0a   je     0xfdb <main()+433>
       0x0000000000000fd1 <+423>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fd5 <+427>:   48 83 e8 10     sub    $0x10,%rax
       0x0000000000000fd9 <+431>:   eb 05   jmp    0xfe0 <main()+438>
       0x0000000000000fdb <+433>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000000fe0 <+438>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    Wir sehen also, dass GCC Folgendes tut:

    • Überprüfen Sie, ob der Zeiger NULL ist, und geben Sie NULL zurück, wenn ja
    • Andernfalls subtrahieren Sie 0x10 davon, um das zu erreichen, Dwas nicht existiert
  • dynamic_cast<D*>(b2s[0]) 0: C ++ hat tatsächlich festgestellt, dass die Besetzung ungültig ist und zurückgegeben wurde nullptr!

    Dies kann zum Zeitpunkt der Kompilierung auf keinen Fall durchgeführt werden, und wir werden dies bei der Demontage bestätigen:

    59          dp = dynamic_cast<D*>(b2s[0]);
       0x00000000000010ec <+706>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x00000000000010f0 <+710>:   48 85 c0        test   %rax,%rax
       0x00000000000010f3 <+713>:   74 1d   je     0x1112 <main()+744>
       0x00000000000010f5 <+715>:   b9 10 00 00 00  mov    $0x10,%ecx
       0x00000000000010fa <+720>:   48 8d 15 f7 0b 20 00    lea    0x200bf7(%rip),%rdx        # 0x201cf8 <_ZTI1D>
       0x0000000000001101 <+727>:   48 8d 35 28 0c 20 00    lea    0x200c28(%rip),%rsi        # 0x201d30 <_ZTI2B2>
       0x0000000000001108 <+734>:   48 89 c7        mov    %rax,%rdi
       0x000000000000110b <+737>:   e8 c0 fb ff ff  callq  0xcd0 <__dynamic_cast@plt>
       0x0000000000001110 <+742>:   eb 05   jmp    0x1117 <main()+749>
       0x0000000000001112 <+744>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000001117 <+749>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    Zuerst gibt es eine NULL-Prüfung, die NULL zurückgibt, wenn die Eingabe NULL ist.

    Andernfalls werden einige Argumente in RDX, RSI und RDI eingerichtet und aufgerufen __dynamic_cast.

    Ich habe nicht die Geduld, dies jetzt weiter zu analysieren, aber wie andere sagten, besteht die einzige Möglichkeit, dies zu erreichen, darin __dynamic_cast, auf einige zusätzliche speicherinterne RTTI-Datenstrukturen zuzugreifen, die die Klassenhierarchie darstellen.

    Es muss daher vom B2Eintrag für diese Tabelle ausgehen und dann diese Klassenhierarchie durchlaufen, bis festgestellt wird, dass die vtable für einen DTypecast von b2s[0].

    Aus diesem Grund ist eine Neuinterpretation der Besetzung möglicherweise teuer! Hier ist ein Beispiel, in dem ein Einzeiler-Patch, dynamic_castder a static_castin ein komplexes Projekt konvertiert , die Laufzeit um 33% reduzierte! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940Dieser glaubt uns nur blind: Wir sagten, es gibt eine Dat-Adresse b2s[1], und der Compiler führt keine Offset-Berechnungen durch.

    Dies ist jedoch falsch, da D tatsächlich bei 0x7fffffffc930 liegt. Was bei 0x7fffffffc940 liegt, ist die B2-ähnliche Struktur in D! So wird auf Müll zugegriffen.

    Wir können dies anhand der schrecklichen -O0Versammlung bestätigen, die den Wert nur bewegt:

    70          dp = reinterpret_cast<D*>(b2s[1]);
       0x00000000000011fa <+976>:   48 8b 45 d8     mov    -0x28(%rbp),%rax
       0x00000000000011fe <+980>:   48 89 45 98     mov    %rax,-0x68(%rbp)

Verwandte Fragen:

Getestet unter Ubuntu 18.04 amd64, GCC 7.4.0.

Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功
quelle