为什么要避免铸造?[关闭]


97

通常我会尽可能避免转换类型,因为我认为这是不良的编码实践,并且可能会导致性能下降。

但是,如果有人要我解释为什么会这样,我可能会像头灯中的鹿一样看着它们。

那么,为什么/何时铸造不好?

它对于Java,C#,C ++是通用的,还是每个不同的运行时环境都按照自己的方式处理?

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


3
您从哪里得到这种印象?
Oded

8
我从没看过一本书,也没有见过一个程序员说“ CASTING SOOOO GOOOOD!”,给我留下了深刻的印象。
LoudNPssWrongly错误

15
C ++的答案不可避免地与C#的答案不同。这是非常特定于语言的。到目前为止的答案回答了针对特定语言的问题,在某些情况下并未说明他们在谈论哪种语言。
James McNellis

2
不好是一个相对术语。避免强制转换是一种最佳实践,但是有时程序员必须要做程序员必须要做的事情。(特别是如果您编写的Java 1.5+程序使用为1.4编写的库),也许可以重命名以下问题:“为什么应该避免强制转换?”
Mike Miller 2010年

2
这是一个很大的问题。...由5个用户组成,每个用户的名声都低于1万!显然,过度使用新权力。已投票重新开放。
到达4thelasers

Answers:


141

您已经用三种语言标记了这三种语言,答案在三种语言之间确实有很大的不同。对C ++的讨论或多或少也意味着对C转换的讨论,这(或多或少)给出了第四个答案。

由于这是您没有明确提及的内容,因此我将从C开始。C强制转换存在许多问题。一个是他们可以做很多不同的事情。在某些情况下,强制转换只不过告诉编译器:“闭嘴,我知道我在做什么”,即,即使在进行可能会导致问题的转换时,它也可以确保编译器不会警告您这些潜在的问题。仅举例来说char a=(char)123456;。定义此实现的确切结果(取决于的大小和签名)char),并且除非在非常奇怪的情况下使用,否则可能没有用。C强制转换也有所不同,它们是仅在编译时发生的事情(即,您只是告诉编译器如何解释/处理某些数据)还是在运行时发生的事情(例如,从double到的实际转换)长)。

C ++尝试通过添加许多“新”强制转换运算符来至少在某种程度上解决该问题,每个运算符仅限于C强制转换功能的一部分。这使得(例如)意外地执行您确实不想要的转换变得更加困难-如果打算在对象上放弃constness,则可以使用const_cast,并确保它唯一会影响的是一个目的是constvolatile或不是。相反,static_cast不允许a 影响对象是否为const或。volatile。简而言之,您具有大多数相同类型的功能,但是将它们归类,因此一个类型转换通常只能执行一种转换,而单个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个。


10
看起来您在这里遭受了“注意力分散死亡”,这是一个不错的答案。
史蒂夫·汤森

2
一个很好的答案,但可以收紧一些“我不知道”的部分,以使其更全面。@ Dragontamer5788这是一个很好的答案,但并不全面。
M2tM 2010年

5
一个完整的孩子和一个腿缺失的孩子将是1.8个孩子。但是,非常好的答案仍然如此。:)
盎司

1
一个很好的答案至少不会因为没有孩子的夫妻而受到阻碍。

@Roger:不排除,只是忽略。
杰里·科芬

48

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

投射通常意味着以下两种情况之一:

  • 我知道此表达式的运行时类型,但编译器不知道。编译器,我告诉您,在运行时,与此表达式相对应的对象实际上就是这种类型的。到目前为止,您已经知道该表达式将被视为这种类型。生成假定对象为给定类型的代码,或者,如果我错了,则引发异常。

  • 编译器和开发人员都知道表达式的运行时类型。在运行时,此表达式将具有与该类型的值关联的另一种不同类型的值。生成从给定类型的值中生成所需类型的值的代码;如果您不能这样做,则抛出异常。

请注意,这些是相反的。有两种类型的演员!在某些类型转换中,您向编译器提供了有关现实提示 -嘿,这种类型的对象实际上是客户类型-在某些类型中,您告诉编译器执行从一种类型到另一种类型的映射 -嘿,我需要对应于此double的int。

两种类型的转换都是危险信号。第一种类型转换提出了一个问题:“为什么开发人员确切知道编译器不知道的东西?” 如果您处在这种情况下,那么更好的选择通常是更改程序,以使编译器确实掌握实际情况。然后,您不需要演员表;分析在编译时完成。

第二种类型转换引发了一个问题:“为什么首先不对目标数据类型进行操作?” 如果您需要以整数为单位的结果,那么为什么首先要保持双精度?您不应该持有int吗?

这里还有一些其他想法:

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


2
我认为这些天生不是危险信号。如果您有一个Foo继承自的对象Bar,并将其存储在中List<Bar>,则需要转换时需要强制转换Foo。也许这表明在体系结构级别存在问题(为什么我们要存储Bars而不是Foos?),但不一定。并且,如果该对象Foo也具有有效的强制转换为int,则它还会处理您的其他注释:您存储的是Foo,而不是int,因为an int并不总是合适的。
Mike Caron 2010年

6
@Mike Caron-显然,我无法为Eric回答,但是对我来说,一个红旗意味着“这是要考虑的问题”,而不是“这是错误的问题”。而且,将a存储Foo在a中没有问题List<Bar>,但是在转换时,您正在尝试对a做Foo不适合的事情Bar。这意味着通过虚拟方法提供的内置多态性以外的机制来实现子类型的不同行为。也许这是正确的解决方案,但更多时候是一个危险信号。
Jeffrey L Whitledge,2010年

14
确实。如果您是从动物列表中抽出东西,后来又需要告诉编译器,哦,对了,我碰巧知道第一个是老虎,第二个是狮子,第三个是老虎。是熊,那么您应该一直使用Tuple <Lion,Tiger,Bear>,而不是List <Animal>。
埃里克·利珀特

5
我同意你的观点,但是我认为,如果没有更好的Tuple<X,Y,...>元组语法支持,C#中就不可能广泛使用它。这是语言可以更好地将人们推向“成功之坑”的地方。
kvb 2010年

4
@kvb:我同意。我们考虑过对C#4采用元组语法,但它不适合预算。也许在C#5中;我们尚未制定出完整的功能集。太忙了将CTP聚集在一起进行异步处理。也许是假设的未来版本。
埃里克·利珀特

36

在Java中,转换错误始终被报告为运行时错误。使用泛型或模板化会将这些错误转换为编译时错误,从而使您更容易发现错误。

正如我上面所说。这并不是说所有转换都是不好的。但是,如果有可能避免,则最好这样做。


4
假设所有转换都是“错误的”;但是,这并非完全正确。以C#为例,它同时具有隐式和显式强制转换支持。当执行强制转换(偶然地)删除信息或类型安全性(因语言而异)时,问题就来了。

5
实际上,这在C ++中完全是错误的。可能需要按顺序进行编辑,以包括此信息所针对的语言。
史蒂夫·汤森

@Erick-我会的,但是我不了解Java的第一件事,也没有足够的C#详细信息来确保信息正确。
史蒂夫·汤森

抱歉,我是Java语言的人,所以我的c ++知识足够有限。我可以编辑其中的“模板”一词,因为它含糊不清。
Mike Miller 2010年

不是这样 Java编译器会捕获某些强制转换错误,而不仅仅是通用错误。考虑(String)0;
罗恩侯爵

18

投射本身并没有什么坏处,只是它经常被误用作实现某些事情的手段,这些东西要么根本不应该做,要么更优雅地做。

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

我的建议是专注于您的主要语言,并了解其所有类型和相关的最佳实践。那应该将短途旅行引入其他语言。

相关的C#文档在这里

这里的上一个SO问题上,有关于C ++选项的精彩摘要。


9

我在这里主要是讲C ++,但是其中大多数可能也适用于Java和C#:

C ++是一种静态类型的语言。该语言允许您使用一些方法(虚拟函数,隐式转换),但是基本上,编译器在编译时就知道每个对象的类型。使用这种语言的原因是可以在编译时捕获错误。如果编译器知道aand 的类型b,那么当您执行a=bwhere a是一个复数并且b是一个字符串时,它将在编译时捕获您。

每当执行显式转换时,您都会告诉编译器关闭,因为您认为自己更了解。万一您错了,通常只能在运行时查找。在运行时查找的问题在于,这可能是在客户的身上。


5

Java,c#和c ++是强类型语言,尽管强类型语言可以被视为不灵活的语言,但它们具有在编译时进行类型检查的优势,并可以防止因某些操作的类型错误而导致运行时错误。

基本上有两种类型的类型转换:更通用类型的类型转换或其他类型(更具体的类型)的类型转换。强制转换为更通用的类型(强制转换为父类型)将使编译时间检查保持不变。但是强制转换为其他类型(更具体的类型)将禁用编译时类型检查,并将由编译器替换为运行时检查。这意味着您不太确定自己编译的代码将正确运行。由于额外的运行时类型检查(Java API已执行强制转换!),因此它对性能的影响也可以忽略不计。


5
并非所有人都在C ++中强制转换“旁路”类型的安全性。
James McNellis

4
并非所有人都在C#中强制转换“旁路”类型的安全性。

5
并非所有人都在Java中强制转换“绕过”类型的安全性。
艾里克·罗伯逊

11
在Casting中,并非所有的Cast都“绕过”类型安全。哦,等等,该标签未涉及某种语言...
sbi 2010年

2
在C#和Java的几乎所有情况下,由于系统将执行运行时类型检查(这不是免费的),因此强制转换会降低性能。在C ++中,dynamic_cast它通常比慢static_cast,因为它通常必须执行运行时类型检查(有一些警告:转换为基本类型很便宜,等等)。
特拉维斯·高克尔

4

某些类型的铸造非常安全高效,以至于甚至根本不认为是铸造。

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

如果您从一个简单的类型(如int)转换为较宽的类型(如long int),那么它通常又很便宜(通常比分配与该类型相同的类型更便宜),并且再次是安全的。

其他类型则更令人烦恼和/或更昂贵。在大多数语言中,从基本类型强制转换为派生类型要么便宜,但极有可能发生严重错误(在C ++中,如果您将static_cast从基本类型转换为派生类型,将很便宜,但是如果基础值不是派生类型,则行为是不确定的,可能非常奇怪)或相对昂贵,并且有引发异常的风险(在C ++中为dynamic_cast,在C#中为显式的从基类转换为派生类,等等)。用Java和C#装箱是这种方法的另一个例子,并且花费更大(考虑到它们的变化远不只是基础值的处理方式而已)。

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

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

一个更具概念性,但也许更重要的原因是,每种转换情况都阻碍了您对代码正确性进行推理的能力:每种情况都是另一个可能出错的地方以及出错的方式。可能会出错,这增加了推论整个系统是否会出错的复杂性。即使每次都证明演员表是安全的,证明这一点也是推理的一部分。

最后,大量使用强制类型转换可能表示在创建对象模型,使用模型或同时使用对象模型时未能充分考虑对象模型:经常在相同的几种类型之间来回投射几乎总是导致无法考虑类型之间的关系用过的。在这里,演员表的坏坏不仅仅在于它们,它是坏事的征兆。


2

程序员越来越倾向于遵循关于语言功能使用的教条式规则(“从不使用XXX!”,“ XXX被认为有害”等),其中XXX的范围从gotos到protected数据成员的指针,从单例到传递对象,值。

按照我的经验,遵循这样的规则可以确保两件事:您将不会是一个糟糕的程序员,也不会是一个优秀的程序员。

更好的方法是挖掘并发现这些全面禁止背后的真相,然后明智地使用这些功能,同时要了解许多情况下它们是完成工作的最佳工具。

“我通常尽量避免使用类型转换”是此类概括规则的一个很好的例子。在许多常见情况下,强制转换至关重要。一些例子:

  1. 与第三方代码进行互操作时(尤其是当该代码盛载typedefs时)。(例如:GLfloat<-> double<-> Real。)
  2. 从派生类到基类的指针/引用的转换:这是如此普遍和自然,以至于编译器将隐式地执行它。如果使其显式提高可读性,则强制转换是向前的一步,而不是向后的一步!
  3. 从基类到派生类的指针/引用的转换:即使在设计良好的代码中,也很常见。(例如:异构容器。)
  4. 内部二进制序列化/反序列化或其他低级代码,需要访问内置类型的原始字节。
  5. 在任何时候,只要自然而然,方便和可读地使用其他类型。(例如:std::size_type-> int。)

当然,在很多情况下,适合使用演员表,同样重要的是要学习它们。由于上面的答案已经很好地指出了其中的一些内容,因此我不会赘述。


1

为了详细说明KDeveloper的答案,它本质上不是类型安全的。使用强制转换不能保证您要转换的对象和强制转换的对象将匹配,并且如果发生这种情况,您将获得运行时异常,这总是一件坏事。

对于C#特别注意,因为它包含isas运算符,所以您有机会(在大多数情况下)确定强制转换是否成功。因此,您应该采取适当的步骤来确定该操作是否成功并正确进行。



1

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


1

如果满足两个条件,则仅将对象转换为某种类型:

  1. 你知道那是那种类型
  2. 编译器没有

这意味着并不是您拥有的所有信息都能在您使用的类型结构中很好地表示出来。这很不好,因为您的实现应在语义上包含您的模型,而在这种情况下显然不是。

现在,当您进行投射时,这可能有两个不同的原因:

  1. 您在表达类型关系方面做得不好。
  2. 语言类型系统根本不足以表达它们。

在大多数语言中,您经常遇到第二种情况。Java中的泛型对C ++模板系统有更多帮助,但很难掌握,即使那样,某些事情可能还是不可能的,或者不值得付出。

因此,您可以说,强制转换是一种肮脏的技巧,可以规避您的问题,以某种特定的语言表达某种特定的类型关系。应避免脏乱。但是,没有他们,你永远活不下去。


0

通常,模板(或泛型)比类型转换更安全。在这方面,我要说的是类型转换的问题是类型安全。但是,还有另一个更微妙的问题,尤其是与垂头丧气有关:设计。至少从我的角度来看,垂头丧气是一种代码味道,表明我的设计可能有问题,我应该进一步研究。为什么这么简单:如果您“正确地”获得了抽象,就根本不需要它!顺便问一下好问题...

干杯!


0

简而言之,一个很好的原因是可移植性。都容纳相同语言的不同体系结构可能具有不同大小的整数。因此,如果我从具有较窄int的ArchA迁移到ArchB,则我可能最多会看到奇怪的行为,而最坏时会看到段错误。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.