Warum sollte static_cast <int> (x) anstelle von (int) x verwendet werden?


667

Ich habe gehört, dass die static_castFunktion dem Casting im C-Stil oder dem einfachen Casting im Funktionsstil vorgezogen werden sollte. Ist das wahr? Warum?


36
Widersprechen Sie Ihrer Ehre, fragte und antwortete .
Graeme Perrow

25
Ich bin anderer Meinung, bei dieser anderen Frage ging es darum, die Unterschiede zwischen den in C ++ eingeführten Casts zu beschreiben. Diese Frage bezieht sich auf die tatsächliche Nützlichkeit von static_cast, die sich geringfügig unterscheidet.
Vincent Robert

2
Wir könnten die beiden Fragen sicherlich zusammenführen, aber was wir aus diesem Thread heraushalten müssten, ist der Vorteil der Verwendung von Funktionen gegenüber Casting im C-Stil, das derzeit nur in einer einzeiligen Antwort im anderen Thread ohne Stimmen erwähnt wird .
Tommy Herbert

6
Diese Frage bezieht sich auf "eingebaute" Typen wie int, während sich diese Frage auf Klassentypen bezieht. Das scheint ein Unterschied zu sein, der signifikant genug ist, um eine gesonderte Erklärung zu verdienen.

9
static_cast ist eigentlich ein Operator, keine Funktion.
ThomasMcLeod

Antworten:


633

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.


30
Sie sollten nicht static_castzum Verwerfen einer Vererbungshierarchie verwenden, sondern dynamic_cast. Das gibt entweder den Nullzeiger oder einen gültigen Zeiger zurück.
David Thornley

41
@ David Thornley: Ich stimme normalerweise zu. Ich glaube, ich habe die Vorbehalte angegeben, die static_castin dieser Situation zu beachten sind . dynamic_castist 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_castschneller ist. In mindestens einigen Umgebungen dynamic_castsind 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.
Euro Micelli

18
Ihre Behauptung über C-Casts ist falsch. Alle C-Casts sind Wertkonvertierungen, die in etwa mit C ++ vergleichbar sind static_cast. Das C-Äquivalent von reinterpret_castist *(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.
R .. GitHub STOP HELPING ICE

11
Ihre gute Antwort richtet sich an den Textkörper des Beitrags. Ich suchte nach einer Antwort auf den Titel "Warum statisches_cast <int> (x) anstelle von (int) x verwenden". Das heißt, für Typ int(und intallein) 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.
chux

31
@chux, für int dynamic_castgilt nicht, aber alle anderen Gründe stehen. Zum Beispiel: Nehmen wir an, es vhandelt sich um einen Funktionsparameter, der als deklariert ist floatund dann (int)vist static_cast<int>(v). Wenn Sie den Parameter jedoch in ändern float*, wird (int)vleise, reinterpret_cast<int>(v)während static_cast<int>(v)es illegal ist und vom Compiler korrekt abgefangen wird.
Euro Micelli

114

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.


3
Sie können auch mit den Klammern wie "(int)" suchen, aber eine gute Antwort und einen gültigen Grund für die Verwendung von Casting im C ++ - Stil.
Mike

4
@Mike, das falsch positive Ergebnisse findet - eine Funktionsdeklaration mit einem einzelnen intParameter.
Nathan Osman

1
Dies kann zu falschen Negativen führen: Wenn Sie eine Codebasis durchsuchen, in der Sie nicht der einzige Autor sind, werden Sie keine C-artigen Besetzungen finden, die andere aus bestimmten Gründen eingeführt haben könnten.
Ruslan

7
Wie würde dies helfen, das Projekt aufzuräumen?
Bilow

Sie würden nicht nach static_cast suchen, da dies höchstwahrscheinlich der richtige ist. Sie möchten static_cast herausfiltern, während Sie nach reinterpret_cast, const_cast und möglicherweise sogar dynamic_cast suchen, da diese Orte anzeigen, die neu gestaltet werden können. C-Cast mischt alles zusammen und gibt Ihnen nicht den Grund für das Casting.
Dragan

78

Kurzum :

  1. static_cast<>() bietet Ihnen die Möglichkeit zur Überprüfung der Kompilierungszeit, C-Style-Besetzung nicht.
  2. static_cast<>()kann leicht überall in einem C ++ - Quellcode entdeckt werden; Im Gegensatz dazu ist die Besetzung von C_Style schwerer zu erkennen.
  3. 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?


21
Ich bin nicht der Meinung, dass dies 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
Todd Lehman

3
@ToddLehman: Danke, aber ich habe es auch nicht gesagt 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
Rika

8
@ToddLehman-Code in diesem Bild verwendet zwei verkettete Casts ( (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.
Öö Tiib

28

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.


26

static_castbedeutet, dass Sie nicht versehentlich const_castoder reinterpret_cast, was eine gute Sache ist.


4
Zusätzliche (wenn auch eher geringfügige) Vorteile gegenüber der Besetzung im C-Stil bestehen darin, dass sie stärker hervorsticht (etwas Schlechtes zu tun sollte hässlich aussehen) und greifbarer ist.
Michael Burr

4
Grep-Fähigkeit ist in meinem Buch immer ein Plus.
Branan

7
  1. Ermöglicht das einfache Auffinden von Casts in Ihrem Code mithilfe von grep oder ähnlichen Tools.
  2. Macht deutlich, welche Art von Besetzung Sie ausführen, und greift auf die Hilfe des Compilers bei der Durchsetzung zurück. Wenn Sie nur Konstanz wegwerfen möchten, können Sie const_cast verwenden, wodurch Sie keine anderen Arten von Konvertierungen durchführen können.
  3. Casts sind von Natur aus hässlich - Sie als Programmierer überschreiben, wie der Compiler Ihren Code normalerweise behandeln würde. Sie sagen zum Compiler: "Ich weiß es besser als Sie." In diesem Fall ist es sinnvoll, dass das Durchführen einer Besetzung eine mäßig schmerzhafte Sache sein sollte und dass sie in Ihrem Code herausragen sollten, da sie eine wahrscheinliche Ursache für Probleme darstellen.

Siehe Effektive C ++ - Einführung


Ich stimme dem für Klassen voll und ganz zu, aber macht die Verwendung von C ++ - Besetzungen für POD-Typen überhaupt Sinn?
Zachary Kraus

Ich glaube schon. Alle drei Gründe gelten für PODs, und es ist hilfreich, nur eine Regel zu haben, anstatt separate für Klassen und PODs.
JohnMcG

Interessanterweise muss ich möglicherweise ändern, wie ich meine Casts in zukünftigen Codes für POD-Typen mache.
Zachary Kraus

7

Es geht darum, wie viel Typensicherheit Sie auferlegen möchten.

Wenn Sie schreiben (bar) foo(was entspricht, reinterpret_cast<bar> foowenn 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!


8
(bar) foo ist nicht gleichbedeutend mit reinterpret_cast <bar> (foo). Die Regeln für "(TYPE) expr" lauten, dass der geeignete C ++ - Stil ausgewählt wird, der verwendet werden soll, einschließlich reinterpret_cast.
Richard Corden

Guter Punkt. Euro Micelli gab die endgültige Antwort auf diese Frage.
Pitarou

1
Auch ist es static_cast<bar>(foo)mit Klammern. Gleiches gilt für reinterpret_cast<bar>(foo).
LF

6

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.


4

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);

4
Warum sollte jemand schreiben static_cast<int>(d), wenn (int)des so viel prägnanter und lesbarer ist? (Ich meine im Fall von Grundtypen, nicht Objektzeiger.)
Todd Lehman

@ gd1 - Warum sollte jemand die Konsistenz über die Lesbarkeit stellen? (eigentlich halb ernst)
Todd Lehman

2
@ToddLehman: Ich denke, dass es für mich keinen Sinn macht, für bestimmte Typen eine Ausnahme zu machen, nur weil sie für Sie etwas Besonderes sind, und ich bin auch nicht einverstanden mit Ihrer Vorstellung von Lesbarkeit. Kürzer bedeutet nicht besser lesbar, wie ich aus dem Bild sehe, das Sie in einem anderen Kommentar gepostet haben.
GD1

2
static_cast ist eine klare und bewusste Entscheidung für eine ganz bestimmte Art der Konvertierung. Es trägt daher zur Klarheit der Absicht bei. Es ist auch sehr praktisch als Marker, um Quelldateien nach Konvertierungen in einer Codeüberprüfung, einem Fehler oder einer Aktualisierungsübung zu durchsuchen.
Persixty

1
@ToddLehman Kontrapunkt: Warum sollte jemand schreiben, (int)dwenn 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_castKonstruktor-Casts und habe sie noch nie verwendet, glaube ich nicht. Ich benutze nur, (C)castswenn ich eilig Debug couts schreibe ...
underscore_d
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.