为什么要避免铸造?

时间:2010-11-12 17:19:14

标签: c# java c++ casting

我通常会尽量避免使用类型,因为我的印象是它的编码习惯很差,可能会导致性能下降。

但是,如果有人让我解释为什么会这样,我可能会把它们看成是头灯中的鹿。

那么为什么/什么时候施展不好?

对于java,c#,c ++来说它是通用的还是每个不同的运行时环境都按照它自己的条件处理它?

欢迎使用任何语言的细节,例如为什么c ++中的错误?

14 个答案:

答案 0 :(得分:131)

你用三种语言标记了这一点,三者之间的答案非常不同。对C ++的讨论或多或少意味着对C演员的讨论,并且(或多或少)给出了第四个答案。

因为它是你没有明确提到的那个,所以我将从C开头.C演员阵容有很多问题。一个是他们可以做许多不同的事情。在某些情况下,演员只会告诉编译器(本质上):“闭嘴,我知道我在做什么” - 即,它确保即使你做了可能导致问题的转换,编译器不会警告你这些潜在的问题。例如,char a=(char)123456;。定义此实现的确切结果(取决于char的大小和签名),除了在相当奇怪的情况下,可能没有用。 C转换也有所不同,它们是否仅在编译时发生(即,您只是告诉编译器如何解释/处理某些数据)或在运行时发生的事情(例如,实际从double转换为长)。

C ++尝试通过添加许多“新”强制转换运算符来处理这种情况,每个运算符仅限于C强制转换的一部分功能。这使得(例如)意外地执行您真正不想要的转换变得更加困难 - 如果您只打算在对象上抛弃const,则可以使用const_cast,并确保它可以影响的唯一事物是对象是constvolatile还是不对。相反,static_cast不允许影响对象是const还是volatile。简而言之,您拥有大多数相同类型的功能,但它们被分类,因此一个演员通常只能进行一种转换,其中单个C风格的演员阵容可以在一次操作中进行两次或三次转换。主要的例外是,可以至少在某些情况下使用dynamic_cast代替static_cast,尽管被写为dynamic_cast,但它会真的以static_cast结尾。例如,您可以使用dynamic_cast遍历或缩小类层次结构 - 但是层次结构的“向上”始终是安全的,因此可以静态地完成,而在层次结构中“向下”转换为“必须安全,所以它是动态完成的。

Java和C#彼此更相似。特别是,他们两个铸造(实际上?)总是一个运行时操作。就C ++强制转换操作符而言,它通常最接近dynamic_cast就真正做到的事情而言 - 即,当您尝试将对象强制转换为某种目标类型时,编译器会插入运行时检查以查看是否允许转换,如果不允许则抛出异常。确切的细节(例如,用于“错误转换”异常的名称)会有所不同,但基本原则仍然大致相似(但是,如果内存服务,Java会将转换应用于少数非对象类型,如{{1}更接近C演员阵容 - 但这些类型的使用很少,1)我不记得这一点,2)即使它是真的,无论如何都无关紧要。)

更普遍地看待事情,情况非常简单(至少是IMO):演员(显然足够)意味着你正在将某种东西从一种类型转换为另一种类型。当/如果你这样做,它提出了一个问题“为什么?”如果你真的想要一些特定类型的东西,你为什么不把它定义为开始的那种类型?这并不是说永远没有理由进行这样的转换,但是只要它发生,它应该提示你是否可以重新设计代码以便在整个过程中使用正确的类型。即使看似无害的转换(例如,在整数和浮点之间)也应该比常见的更加密切地进行检查。尽管它们的看似相似性,但整数应该真正用于“计数”类型的事物和浮动点用于“测量”类型的事物。忽视这种区别是导致一些疯狂的陈述,如“普通美国家庭有1.8个孩子”。即使我们都能看到这种情况如何发生,但事实是 no 家庭有1.8个孩子。他们可能有1或者他们可能有2或者他们可能有更多 - 但从来没有1.8。

答案 1 :(得分:45)

这里有很多好的答案。这是我看待它的方式(从C#角度来看)。

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

  • 我知道这个表达式的运行时类型,但编译器不知道它。编译器,我告诉你,在运行时,对应于这个表达式的对象实际上就属于这种类型。截至目前,您知道此表达式将被视为此类型。生成假定对象属于给定类型的代码,或者如果我错了则抛出异常。

  • 编译器和开发人员都知道表达式的运行时类型。还有另一个与此表达式在运行时将具有的值相关联的值的值。生成从给定类型的值生成所需类型值的代码;如果你不能这样做,那就扔一个例外。

请注意,这些是对立面。有两种演员阵容!有一些演员表你正在向编译器提供关于 reality 提示 - 嘿,这个类型对象的东西实际上是Customer类型的东西 - 并且有一些你要告诉的演员表编译器执行从一种类型到另一种类型的映射 - 嘿,我需要与这个double对应的int。

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

第二种演员提出了一个问题“为什么不首先在目标数据类型中进行操作?”如果你需要一个ints的结果,那你为什么要先拿一个双?你不应该持一个int吗?

这里有一些额外的想法:

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

答案 2 :(得分:34)

在java中,始终将转换错误报告为运行时错误。使用泛型或模板将这些错误转化为编译时错误,使您在出错时更容易检测。

正如我上面所说的那样。这并不是说所有的铸造都很糟糕。但如果有可能避免它,最好这样做。

答案 3 :(得分:17)

施放本身并不是坏事,只是它经常被滥用作为实现某些事情的手段,这些事情要么根本不应该完成,要么更优雅地完成。

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

我的建议是专注于您的主要语言,并了解其所有演员表和相关的最佳实践。这应该告诉其他语言的短途旅行。

相关的C#文档是here

关于C ++选项at a previous SO question here有很好的总结。

答案 4 :(得分:9)

我主要是在这里讨论 C ++ ,但大部分可能也适用于Java和C#:

C ++是 静态类型语言 。语言允许你在这里有一些余地(虚函数,隐式转换),但基本上编译器在编译时知道每个对象的类型。使用这种语言的原因是 错误可以在编译时捕获 。如果编译器知道ab的类型,那么当你执行a=b a是一个复数而{{1}时,它会在编译时抓住你是一个字符串。

每当你进行显式投射时,都要告诉编译器关闭,因为你认为 你知道的更好 。如果您错了,通常只能在运行时找到 。在运行时发现的问题是,这可能是客户的问题。

答案 5 :(得分:5)

Java,c#和c ++是强类型语言,虽然强类型语言可以被视为不灵活,但它们具有在编译时进行类型检查并保护您免受因错误类型导致的运行时错误的好处操作

基本上有两种类型的转换:转换为更一般的类型或转换为其他类型(更具体)。转换为更通用的类型(强制转换为父类型)将使编译时检查保持不变。但是转换为其他类型(更具体的类型)将禁用编译时类型检查,并且将由运行时检查替换为编译器。这意味着您不太确定您编译的代码是否可以正确运行。由于额外的运行时类型检查(Java API充满了强制类型转换!),它也会对性能产生一些微不足道的影响。

答案 6 :(得分:4)

某些类型的铸件是如此安全和有效,甚至根本不被视为铸造。

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

如果你从像int这样的简单类型转换为像long int这样的更宽泛的类型,那么它通常也很便宜(通常不会比赋予相同的类型更昂贵)并且再次是安全的。

其他类型更加充实和/或更昂贵。在大多数语言中,从基类型转换为派生类型要么便宜,但是存在严重错误的高风险(在C ++中,如果从基础到派生的static_cast,它将是便宜的,但如果基础值不是派生类型,行为是未定义的,可能非常奇怪)或相对昂贵且存在引发异常的风险(C ++中的dynamic_cast,C#中显式的基于派生的转换,等等)。 Java和C#中的拳击是另一个例子,而且费用更高(考虑到它们的变化不仅仅是如何处理基础值)。

其他类型的强制转换可能会丢失信息(长整数类型为短整数类型)。

这些风险案例(无论是例外还是更严重的错误)和费用都是避免投票的理由。

更具概念性,但也许更重要的原因是,每个铸造案例都是一个案例,在这种情况下,你能够推断代码正确性的能力受到阻碍:每个案例都是另一个可能出错的地方,以及方式它可能出错会增加推断整个系统是否会出错的复杂性。即使演员每次都证明是安全的,证明这是推理的另一部分。

最后,大量使用强制转换可能表示无法在创建,使用它或两者时很好地考虑对象模型:经常在相同的几种类型之间来回转换几乎总是无法考虑关系使用的类型之间。在这里并不是说演员阵容很糟糕,因为他们是坏事的标志。

答案 7 :(得分:2)

程序员越来越倾向于坚持使用语言功能的教条规则(“从不使用XXX!”,“XXX认为有害”等),其中XXX的范围从goto到指针protected数据成员单身人士按值传递对象。

遵循这样的规则,根据我的经验,确保两件事:你不会是一个糟糕的程序员,也不会成为一个优秀的程序员。

更好的方法是挖掘并揭示这些全面禁止背后的真相核心,然后明智地使用这些功能,同时理解 很多情况,他们是这项工作的最佳工具。

“我通常尽量避免使用类型”是这种过度概括规则的一个很好的例子。在许多常见情况下,演员阵容至关重要。一些例子:

  1. 与第三方代码互操作时(特别是当代码充斥着typedef s时)。 (示例:GLfloat< - > double< - > Real。)
  2. 从派生类转换为基类指针/引用:这是如此常见和自然,编译器将隐式执行它。如果明确提高可读性,则演员是向前迈出的一步,而不是倒退!
  3. 从基类转换为派生类指针/引用:即使在设计良好的代码中也很常见。 (例如:异构容器。)
  4. 内部二进制序列化/反序列化或其他需要访问内置类型的原始字节的低级代码。
  5. 任何时候使用不同类型时更自然,方便和可读。 (示例:std::size_type - > int。)
  6. 当然有很多情况下 不适合使用演员表,而且学习这些也很重要;我不会详细介绍,因为上面的答案很好地指出了其中的一些问题。

答案 8 :(得分:1)

详细说明KDeveloper's answer,它本身并不是类型安全的。使用强制转换时,无法保证您所投射和投射的内容将匹配,如果发生这种情况,您将获得运行时异常,这始终是一件坏事。

特别关注C#,因为它包含isas运算符,您有机会(大部分)确定演员是否会成功。因此,您应该采取适当的步骤来确定操作是否成功并正确进行。

答案 9 :(得分:1)

在C#的情况下,由于在处理值类型时涉及装箱/拆箱开销,因此在进行转换时需要更加小心。

答案 10 :(得分:1)

不确定某人是否已经提到这一点,但在C#中,可以以相当安全的方式使用,并且通常是必要的。假设您收到一个可以是多种类型的对象。使用is关键字,您可以首先确认该对象确实是您要将其强制转换为的类型,然后直接将该对象强制转换为该类型。 (我没有使用Java,但我确信在那里也有一种非常简单的方法)。

答案 11 :(得分:1)

如果满足2个条件,则只将对象强制转换为某种类型:

  1. 你知道它属于那种类型
  2. 编译器没有
  3. 这意味着您所使用的类型结构中并未充分表示您拥有的所有信息。这很糟糕,因为你的实现应该语义包含你的模型,在这种情况下它显然不是。

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

    1. 你在表达类型关系方面做得不好。
    2. 语言类型系统根本不足以表达它们。
    3. 在大多数语言中,您经常遇到第二种情况。 Java中的泛型有点帮助,C ++模板系统甚至更多,但很难掌握,甚至有些事情可能是不可能的,或者只是不值得努力。

      所以你可以说,演员是一个肮脏的黑客,以规避你的问题,以某种特定语言表达某种特定的类型关系。应该避免肮脏的黑客攻击。但是你永远不能没有他们。

答案 12 :(得分:0)

通常模板(或泛型)比强制转换更安全。在这方面,我会说铸造的问题是类型安全。然而,还有另一个更微妙的问题,特别是与向下转型有关:设计。至少从我的角度来看,向下倾斜是一种代码气味,表明我的设计可能出现问题,我应该进一步调查。为什么很简单:如果你“正确”得到抽象,你根本就不需要它!顺便问一下好问题......

干杯!

答案 13 :(得分:0)

简单来说,一个很好的理由是因为便携性。两种容纳相同语言的不同架构可能具有不同大小的整数。因此,如果我从ArchA迁移到具有较窄int的ArchB,我可能会看到最奇怪的行为,并且最坏的情况下会出现错误。

(我显然忽略了与架构无关的字节码和IL。)