Ich vermeide Casting-Typen im Allgemeinen so weit wie möglich, da ich den Eindruck habe, dass es sich um eine schlechte Codierungspraxis handelt und möglicherweise Leistungseinbußen auftreten.
Aber wenn mich jemand fragen würde, warum genau das so ist, würde ich sie wahrscheinlich wie ein Reh im Scheinwerferlicht ansehen.
Warum / wann ist Casting schlecht?
Ist es allgemein für Java, C #, C ++ oder behandelt jede andere Laufzeitumgebung es zu ihren eigenen Bedingungen?
Besonderheiten für eine beliebige Sprache sind willkommen. Beispiel: Warum ist es in C ++ schlecht?
Antworten:
Sie haben dies mit drei Sprachen markiert, und die Antworten sind zwischen den drei wirklich sehr unterschiedlich. Die Diskussion über C ++ impliziert mehr oder weniger auch die Diskussion über C-Casts, und das gibt (mehr oder weniger) eine vierte Antwort.
Da es das ist, was Sie nicht explizit erwähnt haben, beginne ich mit C. C-Casts haben eine Reihe von Problemen. Zum einen können sie verschiedene Dinge tun. In einigen Fällen sagt die Besetzung lediglich dem Compiler (im Wesentlichen): "Halt die Klappe, ich weiß, was ich tue" - dh sie stellt sicher, dass der Compiler auch bei einer Konvertierung Probleme verursacht Ich werde Sie nicht vor diesen möglichen Problemen warnen. Nur zum Beispiel
char a=(char)123456;
. Das genaue Ergebnis dieser Implementierung wird definiert (hängt von der Größe und Signatur von abchar
), und außer in ziemlich seltsamen Situationen, ist wahrscheinlich nicht nützlich. C-Casts unterscheiden sich auch darin, ob sie nur zur Kompilierungszeit (dh Sie sagen dem Compiler nur, wie einige Daten interpretiert / behandelt werden sollen) oder zur Laufzeit (z. B. eine tatsächliche Konvertierung von double in) erfolgen lange).C ++ versucht, dies zumindest teilweise zu bewältigen, indem eine Reihe von "neuen" Cast-Operatoren hinzugefügt werden, von denen jeder nur auf eine Teilmenge der Funktionen eines C-Cast beschränkt ist. Dies macht es schwieriger, (zum Beispiel) versehentlich eine Konvertierung durchzuführen, die Sie wirklich nicht beabsichtigt haben. Wenn Sie nur beabsichtigen, die Konstanz eines Objekts zu beseitigen, können Sie sie verwenden
const_cast
und sicherstellen, dass das einzige, was davon betroffen sein kann, ob ein Objekt istconst
,volatile
oder nicht. Umgekehrtstatic_cast
darf a nicht beeinflussen, ob ein Objekt istconst
oder nichtvolatile
. Kurz gesagt, Sie haben die meisten der gleichen Arten von Funktionen, aber sie sind kategorisiert, sodass eine Besetzung im Allgemeinen nur eine Art von Konvertierung durchführen kann, während eine einzelne Besetzung im C-Stil zwei oder drei Konvertierungen in einem Vorgang ausführen kann. Die primäre Ausnahme ist , dass Sie kann ein verwendendynamic_cast
anstelle von einerstatic_cast
zumindest in einigen Fällen und obwohl sie geschrieben alsdynamic_cast
, es wird wirklich als ein Endestatic_cast
. Zum Beispiel können Siedynamic_cast
eine Klassenhierarchie nach oben oder unten durchlaufen - aber ein Cast "up" der Hierarchie ist immer sicher, so dass dies statisch erfolgen kann, während ein Cast "down" die Hierarchie nicht unbedingt sicher ist, so ist es dynamisch gemacht.Java und C # sind einander viel ähnlicher. Insbesondere bei beiden ist das Gießen (praktisch?) Immer eine Laufzeitoperation. In Bezug auf die C ++ - Cast-Operatoren ist es
dynamic_cast
in Bezug auf das, was wirklich getan wird , normalerweise am nächsten an a. Wenn Sie also versuchen, ein Objekt in einen Zieltyp umzuwandeln, fügt der Compiler eine Laufzeitprüfung ein, um festzustellen, ob diese Konvertierung zulässig ist und eine Ausnahme auslösen, wenn dies nicht der Fall ist. Die genauen Details (z. B. der Name, der für die Ausnahme "Bad Cast" verwendet wird) variieren, aber das Grundprinzip bleibt größtenteils ähnlich (obwohl Java, wenn Speicher dient, Casts auf die wenigen Nicht-Objekttypen anwendet, dieint
viel näher an C liegen Casts - aber diese Typen werden selten genug verwendet, dass 1) ich mich nicht sicher daran erinnere und 2) selbst wenn es wahr ist, es sowieso nicht viel ausmacht).Wenn man die Dinge allgemeiner betrachtet, ist die Situation ziemlich einfach (zumindest IMO): Eine Besetzung (offensichtlich genug) bedeutet, dass Sie etwas von einem Typ in einen anderen konvertieren. Wann / wenn Sie das tun, wirft es die Frage "Warum?" Wenn Sie wirklich möchten, dass etwas ein bestimmter Typ ist, warum haben Sie es nicht zunächst als diesen Typ definiert? Das heißt nicht, dass es nie einen Grund gibt, eine solche Konvertierung durchzuführen, aber jedes Mal, wenn dies geschieht, sollte sich die Frage stellen, ob Sie den Code neu entwerfen können, sodass durchgehend der richtige Typ verwendet wurde. Selbst scheinbar harmlose Konvertierungen (z. B. zwischen Ganzzahl und Gleitkomma) sollten viel genauer als üblich untersucht werden. Trotz ihres AnscheinesÄhnlichkeit, Ganzzahlen sollten wirklich für "gezählte" Arten von Dingen und Gleitkomma für "gemessene" Arten von Dingen verwendet werden. Das Ignorieren der Unterscheidung führt zu einigen verrückten Aussagen wie "Die durchschnittliche amerikanische Familie hat 1,8 Kinder". Obwohl wir alle sehen können, wie das passiert, ist die Tatsache, dass keine Familie 1,8 Kinder hat. Sie könnten 1 oder sie könnten 2 haben oder sie könnten mehr als das haben - aber niemals 1.8.
quelle
Viele gute Antworten hier. So sehe ich das (aus C # -Perspektive).
Casting bedeutet normalerweise eines von zwei Dingen:
Ich kenne den Laufzeittyp dieses Ausdrucks, aber der Compiler kennt ihn nicht. Compiler, ich sage Ihnen, zur Laufzeit wird das Objekt, das diesem Ausdruck entspricht, wirklich von diesem Typ sein. Ab sofort wissen Sie, dass dieser Ausdruck als von diesem Typ zu behandeln ist. Generieren Sie Code, der davon ausgeht, dass das Objekt vom angegebenen Typ ist, oder lösen Sie eine Ausnahme aus, wenn ich falsch liege.
Sowohl der Compiler als auch der Entwickler kennen den Laufzeittyp des Ausdrucks. Dem Wert, den dieser Ausdruck zur Laufzeit hat, ist ein anderer Wert eines anderen Typs zugeordnet. Generieren Sie Code, der den Wert des gewünschten Typs aus dem Wert des angegebenen Typs erzeugt. Wenn Sie dies nicht tun können, lösen Sie eine Ausnahme aus.
Beachten Sie, dass dies Gegensätze sind . Es gibt zwei Arten von Abgüssen! Es gibt Casts, in denen Sie dem Compiler einen Hinweis auf die Realität geben - hey, dieses Typobjekt ist tatsächlich vom Typ Customer - und es gibt Casts, in denen Sie den Compiler anweisen, eine Zuordnung von einem Typ zu einem anderen durchzuführen - hey, Ich brauche das int, das diesem Double entspricht.
Beide Arten von Abgüssen sind rote Fahnen. Die erste Art der Besetzung wirft die Frage auf: "Warum genau weiß der Entwickler etwas, was der Compiler nicht weiß?" Wenn Sie sich in einer solchen Situation befinden, ist es normalerweise besser, das Programm so zu ändern, dass der Compiler die Realität im Griff hat. Dann brauchst du die Besetzung nicht; Die Analyse erfolgt zur Kompilierungszeit.
Die zweite Art der Besetzung wirft die Frage auf: "Warum wird die Operation nicht überhaupt im Zieldatentyp ausgeführt?" Wenn Sie ein Ergebnis in Ints benötigen, warum halten Sie dann überhaupt ein Double? Solltest du nicht einen Int halten?
Einige zusätzliche Gedanken hier:
http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
quelle
Foo
Objekt haben, von dem erbtBar
, und das Sie in einem speichern,List<Bar>
benötigen Sie Casts, wenn Sie dasFoo
zurückhaben möchten . Vielleicht weist es auf ein Problem auf architektonischer Ebene hin (warum speichern wirBar
s anstelle vonFoo
s?), Aber nicht unbedingt. Und wenn dasFoo
auch eine gültige Besetzung hat,int
geht es auch um Ihren anderen Kommentar: Sie speichern einFoo
, keinint
, weil einint
nicht immer angemessen ist.Foo
in einemList<Bar>
, aber an der Stelle der Besetzung, an der Sie versuchen, etwas zu tun,Foo
das für ein nicht geeignet istBar
. Das bedeutet, dass ein unterschiedliches Verhalten für Subtypen durch einen anderen Mechanismus als den eingebauten Polymorphismus erzielt wird, der durch virtuelle Methoden bereitgestellt wird. Vielleicht ist das die richtige Lösung, aber häufiger ist es eine rote Fahne.Tuple<X,Y,...>
Tupel eine breite Verwendung in C # unwahrscheinlich ist. Dies ist ein Ort, an dem die Sprache die Menschen besser in die "Grube des Erfolgs" treiben könnte.Casting-Fehler werden in Java immer als Laufzeitfehler gemeldet. Durch die Verwendung von Generika oder Vorlagen werden diese Fehler in Fehler zur Kompilierungszeit umgewandelt, sodass Sie leichter erkennen können, wenn Sie einen Fehler gemacht haben.
Wie ich oben sagte. Das soll nicht heißen, dass alles Casting schlecht ist. Aber wenn es möglich ist, es zu vermeiden, ist es am besten, dies zu tun.
quelle
Casting ist nicht von Natur aus schlecht, es ist nur so, dass es oft als Mittel missbraucht wird, um etwas zu erreichen, das entweder gar nicht oder eleganter gemacht werden sollte.
Wenn es allgemein schlecht wäre, würden Sprachen es nicht unterstützen. Wie jedes andere Sprachmerkmal hat es seinen Platz.
Mein Rat wäre, sich auf Ihre Primärsprache zu konzentrieren und alle Darsteller und die damit verbundenen Best Practices zu verstehen. Das sollte Ausflüge in andere Sprachen informieren.
Die relevanten C # -Dokumente finden Sie hier .
Bei einer früheren SO-Frage finden Sie hier eine großartige Zusammenfassung der C ++ - Optionen .
quelle
Ich spreche hier hauptsächlich für C ++ , aber das meiste davon gilt wahrscheinlich auch für Java und C #:
C ++ ist eine statisch typisierte Sprache . Es gibt einige Spielräume, die die Sprache Ihnen dabei erlaubt (virtuelle Funktionen, implizite Konvertierungen), aber im Grunde kennt der Compiler den Typ jedes Objekts zur Kompilierungszeit. Der Grund für die Verwendung einer solchen Sprache besteht darin, dass beim Kompilieren Fehler auftreten können . Wenn der Compiler die Typen kennen
a
undb
dann wird es Ihnen zur Compile-Zeit zu fangen , wenn Sie das tun ,a=b
woa
eine komplexe Zahl ist undb
ist eine Zeichenfolge.Wann immer Sie explizites Casting durchführen, weisen Sie den Compiler an, den Mund zu halten, weil Sie glauben , es besser zu wissen . Falls Sie sich irren, werden Sie dies normalerweise erst zur Laufzeit herausfinden . Und das Problem beim Herausfinden zur Laufzeit ist, dass dies möglicherweise bei einem Kunden liegt.
quelle
Java, c # und c ++ sind stark typisierte Sprachen. Obwohl stark typisierte Sprachen als unflexibel angesehen werden können, haben sie den Vorteil, dass sie zur Kompilierungszeit eine Typprüfung durchführen und Sie vor Laufzeitfehlern schützen, die durch den falschen Typ für bestimmte Vorgänge verursacht werden.
Grundsätzlich gibt es zwei Arten von Besetzungen: eine Besetzung für einen allgemeineren Typ oder eine Besetzung für einen anderen Typ (spezifischer). Beim Umwandeln in einen allgemeineren Typ (Umwandeln in einen übergeordneten Typ) bleiben die Überprüfungen der Kompilierungszeit erhalten. Das Casting in andere Typen (spezifischere Typen) deaktiviert jedoch die Überprüfung des Typs zur Kompilierungszeit und wird vom Compiler durch eine Laufzeitprüfung ersetzt. Dies bedeutet, dass Sie weniger sicher sind, dass der kompilierte Code korrekt ausgeführt wird. Aufgrund der zusätzlichen Laufzeit-Typprüfung (die Java-API ist voll von Casts!) Hat dies auch einige vernachlässigbare Auswirkungen auf die Leistung.
quelle
dynamic_cast
ist es im Allgemeinen langsamer alsstatic_cast
, da es im Allgemeinen eine Laufzeitüberprüfung des Typs durchführen muss (mit einigen Einschränkungen: Das Umwandeln in einen Basistyp ist billig usw.).Einige Gussarten sind so sicher und effizient, dass sie oft gar nicht als Guss betrachtet werden.
Wenn Sie von einem abgeleiteten Typ in einen Basistyp umwandeln, ist dies im Allgemeinen recht billig (häufig - abhängig von Sprache, Implementierung und anderen Faktoren - kostengünstig) und sicher.
Wenn Sie von einem einfachen Typ wie einem Int zu einem breiteren Typ wie einem Long Int umwandeln, ist dies wiederum oft recht billig (im Allgemeinen nicht viel teurer als das Zuweisen des gleichen Typs, dem dieser Cast zugewiesen wurde) und wiederum sicher.
Andere Typen sind voller und / oder teurer. In den meisten Sprachen ist das Umwandeln von einem Basistyp in einen abgeleiteten Typ entweder billig, birgt jedoch ein hohes Risiko schwerwiegender Fehler (in C ++ ist es billig, wenn Sie static_cast von Basis zu abgeleitet ableiten, aber wenn der zugrunde liegende Wert nicht vom abgeleiteten Typ ist, ist der Verhalten ist undefiniert und kann sehr seltsam sein) oder relativ teuer und mit dem Risiko, eine Ausnahme auszulösen (dynamic_cast in C ++, explizite Basis-zu-Ableitung-Umwandlung in C # usw.). Das Boxen in Java und C # ist ein weiteres Beispiel dafür und ein noch größerer Aufwand (wenn man bedenkt, dass sie sich mehr ändern als nur, wie die zugrunde liegenden Werte behandelt werden).
Andere Arten von Cast können Informationen verlieren (ein langer Integer-Typ zu einem kurzen Integer-Typ).
Diese Fälle von Risiko (ob Ausnahme oder schwerwiegenderer Fehler) und Kosten sind alles Gründe, um das Gießen zu vermeiden.
Ein konzeptionellerer, aber vielleicht wichtigerer Grund ist, dass jeder Fall des Castings ein Fall ist, in dem Ihre Fähigkeit, über die Richtigkeit Ihres Codes nachzudenken, beeinträchtigt wird: Jeder Fall ist ein anderer Ort, an dem etwas schief gehen kann und wie es funktioniert kann schief gehen und die Komplexität der Ableitung erhöhen, ob das gesamte System schief geht. Selbst wenn die Besetzung jedes Mal nachweislich sicher ist, ist der Beweis, dass dies ein zusätzlicher Teil der Argumentation ist.
Schließlich kann die häufige Verwendung von Casts darauf hinweisen, dass das Objektmodell entweder beim Erstellen, bei der Verwendung oder bei beiden nicht gut berücksichtigt wurde: Das häufige Hin- und Herwechseln zwischen denselben wenigen Typen führt fast immer dazu, dass die Beziehungen zwischen den Typen nicht berücksichtigt werden gebraucht. Hier sind Casts nicht so sehr schlecht, sondern ein Zeichen für etwas Schlechtes.
quelle
Es gibt eine wachsende Tendenz für Programmierer, sich an dogmatische Regeln für die Verwendung von Sprachfunktionen zu halten ("niemals XXX verwenden!", "XXX als schädlich angesehen" usw.), wobei XXX von
goto
s über Zeiger zuprotected
Datenelementen zu Singletons zu vorbeigehenden Objekten reicht Wert.Das Befolgen solcher Regeln stellt meiner Erfahrung nach zwei Dinge sicher: Sie werden weder ein schrecklicher Programmierer sein, noch werden Sie ein großartiger Programmierer sein.
Ein viel besserer Ansatz ist es , zu graben und aufzudecken den Kern der Wahrheit hinter dieser Decke Verboten und dann die Eigenschaften umsichtig, mit dem Verständnis , dass es gibt viele Situationen , in denen sie das beste Werkzeug für den Job sind.
"Ich vermeide Casting-Typen im Allgemeinen so weit wie möglich" ist ein gutes Beispiel für eine solche übergeneralisierte Regel. Abgüsse sind in vielen häufigen Situationen unerlässlich. Einige Beispiele:
typedef
s ist). (Beispiel:GLfloat
<-->double
<-->Real
.)std::size_type
->int
.)Es gibt sicherlich viele Situationen, in denen es nicht angebracht ist, eine Besetzung zu verwenden, und es ist wichtig, diese auch zu lernen. Ich werde nicht zu sehr ins Detail gehen, da die obigen Antworten gute Arbeit geleistet haben und einige davon hervorgehoben haben.
quelle
Um auf die Antwort von KDeveloper einzugehen , ist sie nicht von Natur aus typsicher. Beim Casting gibt es keine Garantie dafür, dass das, von dem Sie gießen und zu dem Sie gießen, übereinstimmt. In diesem Fall erhalten Sie eine Laufzeitausnahme, was immer eine schlechte Sache ist.
In Bezug auf C # haben Sie die Möglichkeit, (größtenteils) zu entscheiden, ob eine Besetzung erfolgreich sein würde oder nicht , da sie die Operatoren
is
und enthältas
. Aus diesem Grund sollten Sie die entsprechenden Schritte ausführen, um festzustellen, ob der Vorgang erfolgreich ist, und entsprechend vorgehen.quelle
Im Fall von C # muss man beim Casting vorsichtiger sein, da beim Umgang mit Werttypen Overheads beim Boxen / Unboxen anfallen.
quelle
Ich bin mir nicht sicher, ob jemand dies bereits erwähnt hat, aber in C # kann Casting auf ziemlich sichere Weise verwendet werden und ist oft notwendig. Angenommen, Sie erhalten ein Objekt, das von verschiedenen Typen sein kann. Mit dem
is
Schlüsselwort können Sie zunächst bestätigen, dass das Objekt tatsächlich von dem Typ ist, in den Sie es umwandeln möchten, und dann das Objekt direkt in diesen Typ umwandeln. (Ich habe nicht viel mit Java gearbeitet, aber ich bin mir sicher, dass es auch dort eine sehr einfache Möglichkeit gibt).quelle
Sie wandeln ein Objekt nur dann in einen Typ um, wenn zwei Bedingungen erfüllt sind:
Dies bedeutet, dass nicht alle Informationen, die Sie haben, in der von Ihnen verwendeten Typstruktur gut dargestellt sind. Dies ist schlecht, da Ihre Implementierung Ihr Modell semantisch umfassen sollte , was in diesem Fall eindeutig nicht der Fall ist.
Wenn Sie eine Besetzung machen, kann dies zwei verschiedene Gründe haben:
In den meisten Sprachen stößt man häufig auf die 2. Situation. Generika wie in Java helfen ein bisschen, das C ++ - Vorlagensystem noch mehr, aber es ist schwer zu beherrschen und selbst dann können einige Dinge unmöglich oder einfach nicht die Mühe wert sein.
Man könnte also sagen, eine Besetzung ist ein schmutziger Hack, um Ihre Probleme zu umgehen und eine bestimmte Typbeziehung in einer bestimmten Sprache auszudrücken. Schmutzige Hacks sollten vermieden werden. Aber ohne sie kann man nie leben.
quelle
Im Allgemeinen sind Vorlagen (oder Generika) typsicherer als Casts. In dieser Hinsicht würde ich sagen, dass ein Problem beim Gießen die Typensicherheit ist. Es gibt jedoch ein anderes subtileres Problem, das insbesondere mit Downcasting verbunden ist: Design. Zumindest aus meiner Sicht ist Downcasting ein Code-Geruch, ein Hinweis darauf, dass etwas mit meinem Design nicht stimmt, und ich sollte weiter nachforschen. Warum ist einfach: Wenn Sie die Abstraktionen richtig "verstehen", brauchen Sie sie einfach nicht! Schöne Frage übrigens ...
Prost!
quelle
Um es kurz zu machen, ein guter Grund ist die Portabilität. Unterschiedliche Architekturen, die beide dieselbe Sprache unterstützen, können beispielsweise unterschiedlich große Ints haben. Wenn ich also von ArchA zu ArchB migriere, das einen engeren int hat, sehe ich möglicherweise bestenfalls ein merkwürdiges Verhalten und im schlimmsten Fall einen Seg-Fehler.
(Ich ignoriere eindeutig architekturunabhängigen Bytecode und IL.)
quelle