Ich habe gehört, dass die static_cast
Funktion dem Casting im C-Stil oder dem einfachen Casting im Funktionsstil vorgezogen werden sollte. Ist das wahr? Warum?
Ich habe gehört, dass die static_cast
Funktion dem Casting im C-Stil oder dem einfachen Casting im Funktionsstil vorgezogen werden sollte. Ist das wahr? Warum?
Antworten:
Der Hauptgrund ist , dass klassische C Abgüsse keinen Unterschied zwischen dem , was wir nennen static_cast<>()
, reinterpret_cast<>()
, const_cast<>()
, und dynamic_cast<>()
. Diese vier Dinge sind völlig unterschiedlich.
A static_cast<>()
ist normalerweise sicher. Es gibt eine gültige Konvertierung in der Sprache oder einen geeigneten Konstruktor, der dies ermöglicht. Das einzige Mal, wenn es ein bisschen riskant ist, ist es, wenn Sie es in eine geerbte Klasse verwandeln. Sie müssen sicherstellen, dass das Objekt tatsächlich der Nachkomme ist, von dem Sie behaupten, dass er es ist, und zwar außerhalb der Sprache (wie eine Flagge im Objekt). A dynamic_cast<>()
ist sicher, solange das Ergebnis überprüft wird (Zeiger) oder eine mögliche Ausnahme berücksichtigt wird (Referenz).
A reinterpret_cast<>()
(oder a const_cast<>()
) dagegen ist immer gefährlich. Sie sagen dem Compiler: "Vertrauen Sie mir: Ich weiß, das sieht nicht wie ein aus foo
(das sieht so aus, als wäre es nicht veränderlich), aber es ist".
Das erste Problem ist, dass es fast unmöglich ist zu sagen, welches in einer Besetzung im C-Stil auftreten wird, ohne große und verstreute Codeteile zu betrachten und alle Regeln zu kennen.
Nehmen wir Folgendes an:
class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;
CMyBase *pSomething; // filled somewhere
Nun werden diese beiden auf die gleiche Weise kompiliert:
CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked
pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
// Safe; as long as we checked
// but harder to read
Sehen wir uns jedoch diesen fast identischen Code an:
CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert
pOther = (CMyOtherStuff*)(pSomething); // No compiler error.
// Same as reinterpret_cast<>
// and it's wrong!!!
Wie Sie sehen, gibt es keine einfache Möglichkeit, zwischen den beiden Situationen zu unterscheiden, ohne viel über alle beteiligten Klassen zu wissen.
Das zweite Problem ist, dass die Casts im C-Stil zu schwer zu finden sind. In komplexen Ausdrücken kann es sehr schwierig sein, Casts im C-Stil zu erkennen. Es ist praktisch unmöglich, ein automatisiertes Tool zu schreiben, das C-ähnliche Casts (z. B. ein Suchwerkzeug) finden muss, ohne ein vollständiges C ++ - Compiler-Frontend. Andererseits ist es einfach, nach "static_cast <" oder "reinterpret_cast <" zu suchen.
pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
// No compiler error.
// but the presence of a reinterpret_cast<> is
// like a Siren with Red Flashing Lights in your code.
// The mere typing of it should cause you to feel VERY uncomfortable.
Das bedeutet, dass nicht nur Casts im C-Stil gefährlicher sind, sondern es auch viel schwieriger ist, sie alle zu finden, um sicherzustellen, dass sie korrekt sind.
static_cast
zum Verwerfen einer Vererbungshierarchie verwenden, sondern dynamic_cast
. Das gibt entweder den Nullzeiger oder einen gültigen Zeiger zurück.
static_cast
in dieser Situation zu beachten sind . dynamic_cast
ist vielleicht sicherer, aber nicht immer die beste Option. Manchmal wissen Sie , dass ein Zeiger auf einen bestimmten Subtyp zeigt, der für den Compiler undurchsichtig ist, und dass a static_cast
schneller ist. In mindestens einigen Umgebungen dynamic_cast
sind optionale Compilerunterstützung und Laufzeitkosten erforderlich (Aktivierung von RTTI), und Sie möchten es möglicherweise nicht nur für ein paar Überprüfungen aktivieren, die Sie selbst durchführen können. RTTI von C ++ ist nur eine mögliche Lösung für das Problem.
static_cast
. Das C-Äquivalent von reinterpret_cast
ist *(destination_type *)&
, dh die Adresse des Objekts zu nehmen, diese Adresse in einen Zeiger auf einen anderen Typ umzuwandeln und dann zu dereferenzieren. Außer im Fall von Zeichentypen oder bestimmten Strukturtypen, für die C das Verhalten dieses Konstrukts definiert, führt dies im Allgemeinen zu einem undefinierten Verhalten in C.
int
(und int
allein) scheint die Verwendung von static_cast<int>
vs. (int)
als einziger Vorteil bei Klassenvariablen und Zeigern zu liegen. Bitten Sie darum, dass Sie dies näher erläutern.
int
dynamic_cast
gilt nicht, aber alle anderen Gründe stehen. Zum Beispiel: Nehmen wir an, es v
handelt sich um einen Funktionsparameter, der als deklariert ist float
und dann (int)v
ist static_cast<int>(v)
. Wenn Sie den Parameter jedoch in ändern float*
, wird (int)v
leise, reinterpret_cast<int>(v)
während static_cast<int>(v)
es illegal ist und vom Compiler korrekt abgefangen wird.
Ein pragmatischer Tipp: Sie können einfach nach dem Schlüsselwort static_cast in Ihrem Quellcode suchen, wenn Sie das Projekt aufräumen möchten.
int
Parameter.
Kurzum :
static_cast<>()
bietet Ihnen die Möglichkeit zur Überprüfung der Kompilierungszeit, C-Style-Besetzung nicht.static_cast<>()
kann leicht überall in einem C ++ - Quellcode entdeckt werden; Im Gegensatz dazu ist die Besetzung von C_Style schwerer zu erkennen.- Absichten werden mit C ++ Casts viel besser vermittelt.
Weitere Erläuterungen :
Die statische Umwandlung führt Konvertierungen zwischen kompatiblen Typen durch . Es ähnelt der Besetzung im C-Stil, ist jedoch restriktiver. Beispielsweise würde die Umwandlung im C-Stil einem ganzzahligen Zeiger erlauben, auf ein Zeichen zu zeigen.
char c = 10; // 1 byte int *p = (int*)&c; // 4 bytes
Da dies zu einem 4-Byte-Zeiger führt, der auf 1 Byte des zugewiesenen Speichers zeigt, verursacht das Schreiben in diesen Zeiger entweder einen Laufzeitfehler oder überschreibt einen benachbarten Speicher.
*p = 5; // run-time error: stack corruption
Im Gegensatz zur Umwandlung im C-Stil kann der Compiler bei der statischen Umwandlung überprüfen, ob die Zeigertypen und Zeigerdatentypen kompatibel sind, sodass der Programmierer diese falsche Zeigerzuweisung während der Kompilierung abfangen kann.
int *q = static_cast<int*>(&c); // compile-time error
Lesen Sie mehr darüber:
Was ist der Unterschied zwischen static_cast <> und C-Casting
und
Regular Cast vs. Static_cast vs. Dynamic_cast?
static_cast<>()
besser lesbar ist. Ich meine, manchmal ist es das, aber die meiste Zeit - besonders bei einfachen Ganzzahltypen - ist es einfach schrecklich und unnötig ausführlich. Beispiel: Dies ist eine Funktion, die die Bytes eines 32-Bit-Wortes vertauscht. Es wäre fast unmöglich, mit static_cast<uint##>()
Casts zu lesen , aber mit Casts ist es ziemlich einfach zu verstehen (uint##)
. Bild des Codes: imgur.com/NoHbGve
always
. (aber meistens ja) Es gibt sicher Fälle, in denen die Besetzung im C-Stil viel besser lesbar ist. Das ist einer der Gründe, warum Casting im C-Stil immer noch live ist und in c ++ imho antritt. :) Übrigens war das ein sehr schönes Beispiel
(uint32_t)(uint8_t)
), um zu erreichen, dass Bytes neben dem niedrigsten zurückgesetzt werden. Dafür gibt es bitweise und ( 0xFF &
). Die Verwendung von Abgüssen verschleiert die Absicht.
Die Frage ist größer als nur die Verwendung von wither static_cast oder C-Casting, da bei der Verwendung von C-Castings verschiedene Dinge passieren. Die C ++ - Casting-Operatoren sollen diese Operationen expliziter machen.
Auf der Oberfläche erscheinen statische Cast- und C-Casts gleich, beispielsweise wenn ein Wert in einen anderen umgewandelt wird:
int i;
double d = (double)i; //C-style cast
double d2 = static_cast<double>( i ); //C++ cast
Beide wandeln den ganzzahligen Wert in ein Double um. Bei der Arbeit mit Zeigern wird es jedoch komplizierter. einige Beispiele:
class A {};
class B : public A {};
A* a = new B;
B* b = (B*)a; //(1) what is this supposed to do?
char* c = (char*)new int( 5 ); //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error
In diesem Beispiel (1) ist es vielleicht in Ordnung, weil das Objekt, auf das A zeigt, wirklich eine Instanz von B ist. Aber was ist, wenn Sie an diesem Punkt im Code nicht wissen, worauf a tatsächlich zeigt? (2) vielleicht vollkommen legal (Sie möchten nur ein Byte der ganzen Zahl betrachten), aber es könnte auch ein Fehler sein, in welchem Fall ein Fehler nett wäre, wie (3). Die C ++ - Casting-Operatoren sollen diese Probleme im Code aufdecken, indem sie nach Möglichkeit Kompilierungs- oder Laufzeitfehler bereitstellen.
Für striktes "Value Casting" können Sie also static_cast verwenden. Wenn Sie ein polymorphes Casting von Zeigern zur Laufzeit wünschen, verwenden Sie dynamic_cast. Wenn Sie Typen wirklich vergessen möchten, können Sie reintrepret_cast verwenden. Und um const einfach aus dem Fenster zu werfen, gibt es const_cast.
Sie machen den Code nur expliziter, sodass Sie anscheinend wissen, was Sie getan haben.
static_cast
bedeutet, dass Sie nicht versehentlich const_cast
oder reinterpret_cast
, was eine gute Sache ist.
Siehe Effektive C ++ - Einführung
Es geht darum, wie viel Typensicherheit Sie auferlegen möchten.
Wenn Sie schreiben (bar) foo
(was entspricht, reinterpret_cast<bar> foo
wenn Sie keinen Typkonvertierungsoperator angegeben haben), weisen Sie den Compiler an, die Typensicherheit zu ignorieren und einfach das zu tun, was ihm gesagt wurde.
Wenn Sie schreiben static_cast<bar> foo
, fordern Sie den Compiler auf, zumindest zu überprüfen, ob die Typkonvertierung sinnvoll ist, und bei integralen Typen einen Konvertierungscode einzufügen.
EDIT 2014-02-26
Ich habe diese Antwort vor mehr als 5 Jahren geschrieben und sie falsch verstanden. (Siehe Kommentare.) Aber es gibt immer noch positive Stimmen!
static_cast<bar>(foo)
mit Klammern. Gleiches gilt für reinterpret_cast<bar>(foo)
.
C-Style-Casts sind in einem Codeblock leicht zu übersehen. Casts im C ++ - Stil sind nicht nur eine bessere Praxis. Sie bieten ein viel höheres Maß an Flexibilität.
reinterpret_cast ermöglicht Konvertierungen von Integral- zu Zeigertyp, kann jedoch bei Missbrauch unsicher sein.
static_cast bietet eine gute Konvertierung für numerische Typen, z. B. von Aufzählungen in Ints oder Ints in Floats oder in Datentypen, deren Typ Sie sicher sind. Es werden keine Laufzeitprüfungen durchgeführt.
dynamic_cast hingegen führt diese Überprüfungen durch, um mehrdeutige Zuweisungen oder Konvertierungen zu kennzeichnen. Es funktioniert nur mit Zeigern und Referenzen und verursacht einen Overhead.
Es gibt ein paar andere, aber dies sind die wichtigsten, auf die Sie stoßen werden.
static_cast kann neben der Bearbeitung von Zeigern auf Klassen auch zum Ausführen von Konvertierungen verwendet werden, die explizit in Klassen definiert sind, sowie zum Ausführen von Standardkonvertierungen zwischen grundlegenden Typen:
double d = 3.14159265;
int i = static_cast<int>(d);
static_cast<int>(d)
, wenn (int)d
es so viel prägnanter und lesbarer ist? (Ich meine im Fall von Grundtypen, nicht Objektzeiger.)
(int)d
wenn int{d}
es so viel besser lesbar ist? Die ()
Syntax eines Konstruktors oder einer Funktion ist nicht annähernd so schnell, dass sie sich in komplexen Ausdrücken in ein alptraumhaftes Labyrinth aus Klammern verwandelt. In diesem Fall wäre es int i{d}
statt int i = (int)d
. Weitaus besser IMO. Das heißt, wenn ich nur ein temporäres Element in einem Ausdruck benötige, verwende ich static_cast
Konstruktor-Casts und habe sie noch nie verwendet, glaube ich nicht. Ich benutze nur, (C)casts
wenn ich eilig Debug cout
s schreibe ...