为什么要避免铸造?

Lou*_*ong 94 c# c++ java casting

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

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

那么为什么/什么时候铸造不好?

它是一般的java,c#,c ++还是每个不同的运行时环境都按照它自己的条件处理它?

任何语言的细节都是受欢迎的,例如为什么它在c ++中不好?

Jer*_*fin 136

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

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

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

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

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

  • 看起来你在这里遭受"注意力的死亡",这是一个很好的答案imo. (10认同)
  • 一个孩子和一个缺少腿的孩子将是1.8个孩子.但非常好的回答.:) (5认同)
  • 一个强有力的答案,但一些"我不知道"的部分可以收紧,以使其全面.@ Dragontamer5788这是一个很好的答案,但不全面. (2认同)

Eri*_*ert 46

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

铸造通常意味着两件事之一:

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

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

请注意,这些是对立的.有两种演员阵容!有一些强制转换,你可以向编译器提供有关现实提示 - 嘿,类型对象的这个东西实际上是Customer类型 - 并且有一些强制转换,你告诉编译器执行从一种类型到另一种类型的映射 - 嘿,我需要与这个double对应的int.

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

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

还有一些额外的想法:

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

  • 确实.如果你从动物列表中提取东西,你后来需要告诉编译器哦,顺便说一句,我碰巧知道第一个是老虎,第二个是狮子,第三个是是熊,那么你应该使用元组<狮子,虎,熊>,而不是列表<动物>. (13认同)
  • @Mike Caron - 显然,我无法回答Eric,但对我来说,红旗意味着"这是值得考虑的事情",而不是"这是错误的".并且在`List <Bar>`中存储`Foo`没有问题,但是在演员阵容中你试图用`Foo`做一些不适合'Bar`的事情.这意味着子类型的不同行为是通过虚拟方法提供的内置多态性以外的机制完成的.也许这是正确的解决方案,但更多时候它是一个红旗. (6认同)
  • 我同意你的意见,但我认为如果没有更好的语法支持`Tuple <X,Y,...>`元组不太可能在C#中广泛使用.这是一个语言可以更好地推动人们走向"成功的障碍"的地方. (5认同)
  • @kvb:我同意.我们考虑为C#4采用元组语法,但它不符合预算.也许在C#5; 我们还没有制定完整的功能集.太忙于将CTP一起用于异步.或者在假设的未来版本中. (4认同)
  • 我不认为那些本身就是红旗.如果你有一个继承自`Bar`的`Foo`对象,并将它存储在`List <Bar>`中,那么如果你想要那个'Foo`,你将需要强制转换.也许它表明了建筑层面的问题(为什么我们存储`Bar`s而不是'Foo`s?),但不一定.而且,如果`Foo`也有一个有效的强制转换为`int`,它也会处理你的另一个注释:你存储一个`Foo`,而不是一个`int`,因为`int`并不总是合适的. (2认同)
  • @Bill:然后我会使用`OfType <T>`扩展方法而不是强制转换.`foreach(Tiger t in animals.OfType <Tiger>())`非常清楚.或者我会让基类有一个执行特殊行为的虚方法; 那么你不需要进行强制转换然后执行实例调度; 你只需要进行虚拟调度. (2认同)

Mik*_*ler 35

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

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

  • 实际上,这在C++中是错误的.也许编辑包含以此信息为目标的语言. (5认同)
  • 这假设所有铸件都是"坏"; 然而,这并非完全正确.以C#为例,同时使用隐式和显式转换支持.当执行演员表(意外地)删除信息或类型安全(这因语言而异)时会出现问题. (4认同)

Ste*_*end 18

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

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

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

相关的C#文档在这里.

在此前的SO问题中,对C++选项有一个很好的总结.


sbi*_*sbi 9

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

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

无论何时进行显式转换,都要告诉编译器闭嘴,因为你认为你知道的更好.如果你错了,你通常只会在运行时发现.在运行时发现的问题是,这可能是客户的问题.


Kde*_*per 5

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

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

  • 并非所有演员都在Casting中"绕过"类型安全.哦等等,那个标签不是指语言...... (11认同)
  • 并非所有演员都在C++中"绕过"类型安全. (5认同)
  • 并非所有演员都在Java中"绕过"类型安全. (5认同)
  • 并非所有演员都在C#中"绕过"类型安全. (4认同)
  • 在C#和Java的几乎所有情况下,由于系统将执行运行时类型检查(不是免费的),因此转换会降低性能.在C++中,`dynamic_cast`通常比`static_cast`慢,因为它通常必须进行运行时类型检查(有一些注意事项:转换为基类型是便宜的等等). (2认同)