为什么要避免铸造?

我通常尽可能地避免使用types,因为我认为这是糟糕的编码习惯,可能会导致性能损失。

但是如果有人问我解释为什么这样,我可能会把他们看成是头灯上的一只鹿。

那么,为什么/何时投射不好呢?

是通用的Java,C#,C + +或每个不同的运行时环境处理它自己的条款?

欢迎任何语言的具体细节,例如为什么它在C + +不好?

你用三种语言给这个标签加了标签,三者之间的答案真的很不一样。 对C ++的讨论或多或less意味着对C语言的讨论,并给出(或多或less)第四个答案。

由于这是你没有明确提到的那个,我将从C开始。C演员有许多问题。 一个是他们可以做许多不同的事情。 在某些情况下,转换只不过是告诉编译器(本质上):“闭嘴,我知道我在做什么” – 也就是说,即使你做了可能导致问题的转换,编译器也能确保编译器不会提醒你这些潜在的问题。 举个例子, char a=(char)123456; 。 这个实现的确切结果定义(取决于char的大小和符号),除了在相当奇怪的情况下,可能没有用。 C的types转换也不尽相同,只是在编译时(即,你只是告诉编译器如何解释/处理一些数据),或者是在运行时发生的事情(例如,实际从double长)。

C ++至less在某种程度上试图通过添加一些“新”转换操作符来处理这个操作符,每个操作符都被限制在一个Ctypes的能力的一个子集上。 这使得更难以(例如)意外地进行一个你并不打算的转换 – 如果你只是想抛弃一个对象的const,你可以使用const_cast ,并且确保它唯一可以影响的是无论对象是const还是volatile ,或者不是。 相反, static_cast不允许影响对象是const还是volatile 。 总之,你拥有大部分相同types的function,但是它们被分类,所以一个转换通常只能进行一种转换,其中一个C风格的转换可以在一个操作中进行两个或三个转换。 主要的例外是,至less在某些情况下,您可以使用dynamic_cast代替static_cast ,尽pipe被写为dynamic_cast ,但实际上最终会以static_cast 。 例如,你可以使用dynamic_cast来遍历一个类的层次结构 – 但是一个强制转换的层次结构总是安全的,所以它可以静态地完成,而一个强制转换的层次结构不一定是安全的。它是dynamic完成的。

Java和C#更加相似。 特别是,他们两个都是(几乎?)总是一个运行时的操作。 就C ++types转换运算符而言,它通常最接近dynamic_cast ,就实际完成情况而言 – 也就是说,当您尝试将某个对象转换为某个目标types时,编译器会插入运行时检查以查看该转换是否允许,如果不是,则抛出exception。 确切的细节(例如,用于“坏转换”exception的名称)各不相同,但是其基本原理大部分仍然相似(尽pipe如果内存起作用,Java确实会将转换应用于像int这样的非对象types, Ctypes – 但是这些types的用法很less:1)我不记得那是肯定的,2)即使它是真的,反正也没有关系)。

更一般地看待事情,情况很简单(至lessIMO):一个演员(显然是足够的)意味着你正在从一种types转换到另一种types。 当/如果你这样做,就会提出“为什么?”这个问题。 如果你真的想要某种特定的types,为什么不把它定义为这种types呢? 这并不是说没有任何理由去做这样的转换,但是每当它发生的时候,它都会提示你是否可以重新devise代码,以便在整个过程中使用正确的types。 即使看起来无害的转换(例如整数和浮点之间的转换)也应该比普通转换得更密切。 尽pipe它们看似相似,整数应该被用于“计数”types的事物和“测量”types事物的浮点。 忽略这个区别是导致一些疯狂的说法,比如“一般的美国家庭有1.8个孩子”。 即使我们都可以看到这种情况,事实上, 没有一个家庭有1.8个孩子。 他们可能有1个,也可能有2个,或者他们可能有更多的 – 但从来没有1.8。

很多好的答案在这里。 这是我看的方式(从C#的angular度来看)。

投射通常意味着两件事之一:

  • 我知道这个expression式的运行时types,但编译器不知道它。 编译器,我告诉你,在运行时,对应于这个expression式的对象实际上将是这种types的。 到目前为止,你知道这个expression式被视为这种types。 生成假定对象将是给定types的代码,或者,如果我错了,则抛出exception。

  • 编译器和开发人员都知道expression式的运行时types。 还有另外一个与这个expression式在运行时会有的值有关的不同types的值。 生成从给定types的值生成所需types值的代码; 如果你不能这样做,那就抛出一个exception。

注意那些是相反的 。 有两种演员! 有些地方你正在向编译器提供关于现实暗示 – 嘿,这个objecttypes的东西实际上是Customertypes的,而且有一些是在告诉编译器执行从一个types到另一个types的映射 – 嘿,我需要对应于这个double的int。

这两种演员都是红旗。 第一种演员提出了“为什么开发人员知道编译器不知道的东西?”这个问题。 如果你处于这种情况,那么通常要改变程序,以便编译器能够处理现实。 那么你不需要演员; 分析是在编译时完成的。

第二种types提出了“为什么目标数据types中的操作不是首先完成的? 如果你需要一个整数的结果,那么你为什么在第一个地方举行双? 你不应该保持一个int吗?

这里有一些额外的想法

http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/

铸造错误总是作为java中的运行时错误报告。 使用generics或模板将这些错误转化为编译时错误,使得在发现错误时更容易检测。

正如我上面所说。 这并不是说所有的铸造都是坏的。 但如果可以避免的话,最好这样做。

铸造本质上并不坏,只是它经常被滥用作为一种手段来实现真正应该完成的任务,或者做得更加优雅。

如果普遍不好,语言不会支持它。 像任何其他语言function一样,它也有它的位置。

我的build议是把重点放在你的主要语言,并理解所有的演员和相关的最佳做法。 这应该通知其他语言的游览。

相关的C#文档在这里 。

这里有一个关于C ++选项的很好的总结。

我主要是为了C ++而在这里讲话,但是这大部分可能也适用于Java和C#

C ++是一种静态types的语言 。 语言允许你在这个(虚拟函数,隐式转换)中有一些余地,但基本上编译器在编译时知道每个对象的types。 使用这种语言的原因是错误可以在编译时被捕获 。 如果编译器知道ab的types,那么当你执行a=b时,它会在编译时捕获你,其中a是一个复数, b是一个string。

每当你进行明确的转换,你都会告诉编译器闭嘴,因为你认为你知道的更好 。 如果你错了,通常你只能在运行时查出来。 在运行时发现的问题是,这可能是在客户的。

Java,c#和c ++是强types语言,尽pipe强types语言可以被看作是不灵活的,但是它们有利于在编译时进行types检查,并保护您免受由于某些操作具有错误types而导致的运行时错误。

基本上有两种types的转换:一种转换为更一般的types,或者转换为其他types的转换(更具体)。 转换为更一般的types(转换为父types)会使编译时间检查保持不变。 但是,转换为其他types(更具体的types)将禁用编译时types检查,并由编译器通过运行时检查来取代。 这意味着你不太确定你编译的代码是否能够正常运行。 由于额外的运行时types检查(Java API充满了强制转换!),它也具有一些可以忽略的性能影响。

某些types的铸件如此安全和高效,甚至经常不被认为是铸造。

如果从派生types转换为基types,这通常很便宜(通常 – 取决于语言,实现和其他因素 – 这是零成本),并且是安全的。

如果你从一个简单的types像一个inttypes转换为一个更长的types,那么它通常是相当便宜的(通常不会比分配相同的types更昂贵),而且是安全的。

其他types更加昂贵和/或更昂贵。 在大多数语言中,从基本types转换为派生types或者便宜,但是具有严重错误的高风险(在C ++中,如果static_cast从基类到派生types将是便宜的,但是如果基础值不是派生types,行为是未定义的,而且可能非常奇怪),或者相对昂贵,并且存在引发exception(C ++中的dynamic_cast,C#中显式的基类派生types转换等等)的风险。 在Java和C#中的拳击是另一个例子,这是一个更大的代价(考虑到他们正在改变的不仅仅是如何处理底层的价值观)。

其他types的转换可能会丢失信息(一个长整数types为一个短整数types)。

这些风险(无论是例外还是更严重的错误)和费用都是避免投资的原因。

一个更概念的,但也许更重要的原因是,每个案件的铸造是一个情况下,你的代码的正确性的推理能力是困难的:每个案件是另一个地方可能出现问题的地方,以及它的方式可能出现的错误增加了推断系统是否会出错的复杂性。 即使演员每次certificate是安全的,certificate这是推理的额外部分。

最后,强制使用强制转换可能表明无法很好地考虑对象模型,无论是在创build,使用它还是在两者之间:经常在相同的几个types之间来回转换几乎总是无法考虑types之间的关系用过的。 在这里,演员不是很糟糕,因为他们是坏事的标志。

程序员们越来越倾向于使用语言特性(“从不使用XXX”,“XXX认为有害”等)的教条式规则,其中XXX的范围从goto protected数据成员指向单身传递对象的价值。

遵循这样的规则,根据我的经验,确保两件事情:你不会是一个可怕的程序员,也不会成为一个伟大的程序员。

一个更好的方法是挖掘并揭露这些禁令背后的真相核心,然后明智地使用这些特征,并且了解到有很多情况下他们是最好的工具。

“我通常尽可能避免使用铸造types”就是这样一个过于普遍化的规则的一个很好的例子。 演员在许多常见的情况下是必不可less的。 一些例子:

  1. 与第三方代码进行互操作时(特别是当这些代码充斥着typedef )。 (例如: GLfloat < – > double < – > Real 。)
  2. 从派生类到基类指针/引用的转换:这是非常普遍和自然的,编译器会隐式地执行它。 如果明确提高可读性,演员阵容是向前迈出的一步,而不是倒退!
  3. 从基础转换到派生类指针/引用:即使在devise良好的代码中也很常见。 (例如:异构容器)
  4. 内部二进制序列化/反序列化或其他需要访问内置types的原始字节的底层代码。
  5. 任何时候,只要简单一些,就可以使用不同types的自然,方便和易读。 (例如: std::size_type – > int 。)

当然,很多情况下使用演员阵容是不合适的,学习这些也是很重要的。 上面的答案已经做了一些很好的工作,我不会过多的细节。

为了详细说明KDeveloper的答案 ,它本质上不是types安全的。 通过施法,不能保证你正在施放和施放的东西是否匹配,如果发生这种情况,你将会得到一个运行时exception,这总是一件坏事。

关于C#,因为它包含了as操作符,所以你有机会(大部分)决定一个演员是否会成功。 因此,您应该采取适当的步骤来确定操作是否成功并适当地进行。

在C#的情况下,由于在处理值types时涉及的装箱/拆箱开销,因此在投射时需要更小心。

不知道如果有人已经提到这一点,但在C#铸造可以用一个相当安全的方式,而且往往是必要的。 假设你收到一个可以有多种types的对象。 使用is关键字可以首先确认该对象确实是您要将其转换为的types,然后直接将对象转换为该types。 (我没有太多的Java工作,但我相信在这里也有一个非常简单的方法)。

如果满足2个条件,则只能将某个对象转换为某种types:

  1. 你知道它是那种types
  2. 编译器没有

这意味着你所使用的types结构中并不是所有的信息都能很好地performance出来。 这是不好的,因为你的实现应该在语义上构成你的模型,在这种情况下它显然不是。

现在当你演员演员时,这可能有两个不同的原因:

  1. 你在expressiontypes关系方面做得不好。
  2. 语言types系统根本不足以expression它们。

在大多数语言中,你会遇到很多次第二种情况。 generics在Java中有点帮助,C ++模板系统更多,但是很难掌握,甚至有些东西可能是不可能的,或者是不值得的。

所以你可以说,演员是一个肮脏的黑客来规避你的问题,以某种特定的语言expression某种特定的types关系。 肮脏的黑客应该避免。 但是你永远不能没有他们的生活。

通常模板(或generics)比types更安全。 在这方面,我会说铸造的问题是types安全。 但是,还有另一个更加微妙的问题,特别是与向下转换:devise。 至less从我的angular度来说,向下转换是一种代码异味,表明我的devise中可能有些问题,我应该进一步调查。 为什么很简单:如果你“获得”抽象的权利,你根本就不需要它! 好的问题的方式…

干杯!

要真的简洁,一个很好的理由是因为可移植性。 不同的架构都适用于相同的语言可能有不同的大小整数。 因此,如果我从ArchA移植到ArchB,而ArchB的诠释范围更窄,我可能会看到奇怪的行为,最坏的情况是Seg错误。

(我显然忽略了独立于体系结构的字节码和IL。)