为什么在C ++中不隐式转换`void *`?


30

在C语言中,不需要将a强制转换void *为任何其他指针类型,因此始终可以安全地对其进行提升。但是,在C ++中并非如此。例如,

int *a = malloc(sizeof(int));

在C语言中有效,但在C ++中无效。(请注意:我知道您不应该malloc在C ++中使用,或者在这种情况下new,应该使用智能指针和/或STL;纯粹出于好奇而问)。为什么C ++标准不允许这种隐式转换,而C标准呢?


3
long *a = malloc(sizeof(int));糟糕,有人忘记只更改一种类型!
Doval 2015年

4
@Doval:使用仍然可以轻松解决sizeof(*a)
wolfPack88 2015年

3
我相信@ratchetfreak的观点是C进行这种隐式转换的原因是因为malloc无法返回指向分配的类型的指针。 new如果C ++确实返回了指向分配的类型的指针,那么正确编写的C ++代码将永远不会有任何void *强制转换。

3
顺便说一句,它不是任何其他指针类型。仅数据指针需要应用。
Deduplicator

3
@ wolfPack88您的历史记录有误。C ++了void,C确实没有。当该关键字/思想被添加到C中时,他们对其进行了更改以适应C的需求。那是在完全检查指针类型之后不久。看看您是否可以在线找到K&R C描述手册,或诸如Waite Group的C Primer之类的C编程文本的老式副本。ANSI C充满了C ++向后移植或受其启发的功能,而K&R C则简单得多。因此,C ++扩展了当时存在的C语言,并且将您知道的C语言从C ++中剥离出来,这是更正确的做法。
JDługosz

Answers:


39

因为隐式类型转换通常是不安全的,并且C ++的键入立场比C更为安全。

即使大多数情况下转换是错误的,C也会通常允许隐式转换。这是因为C假定程序员完全知道自己在做什么,如果不是,那是程序员的问题,而不是编译器的问题。

C ++通常会禁止可能存在错误的事情,并要求您使用类型强制转换明确声明您的意图。那是因为C ++试图变得对程序员友好。

您可能会问,当实际上需要您键入更多内容时,它如何变得友好。

好吧,您知道,在任何程序中,以任何编程语言编写的任何给定代码行,其读取次数通常都比其写入(*)的次数要多得多。因此,易于阅读比易于写作更为重要。在阅读时,通过显式类型转换突出任何潜在的不安全转换,有助于理解正在发生的事情,并在一定程度上确定正在发生的事情实际上是预期发生的事情。

此外,与费时费力地排查问题以查找错误(由于您可能已经警告过,但从未警告过)引起的错误相比,不得不键入显式强制转换的不便是微不足道的。

(*)理想情况下,它只会被编写一次,但是每次有人需要对其进行审查以确定其是否适合重用,每次进行故障排除以及每次有人需要在其附近添加代码时,都会读取一次,然后每次对附近的代码进行故障排除,等等。除“一次写入,运行然后丢弃”脚本外,在所有情况下都是如此,因此也就不足为奇了,大多数脚本语言的语法都易于编写,而完全不顾阅读。是否曾经想到过perl完全不可理解?你不是一个人。将此类语言视为“只写”语言。


C本质上是比机器代码高一级的步骤。我可以为这样的事情原谅C。
Qix

8
程序(无论使用哪种语言)均应被读取。显式操作脱颖而出。
Matthieu M.

10
值得注意的是,强制转换在C ++ void*更加不安全,因为使用C ++实现某些OOP功能的方式,指向同一对象的指针可能会根据指针类型而具有不同的值。
海德

@MatthieuM。非常真实 感谢您添加,值得作为答案的一部分。
Mike Nakis

1
@MatthieuM .:啊,但是您真的不想明确地做所有事情。读取更多内容不会增强可读性。尽管在这一点上,平衡显然是明确的。
Deduplicator

28

这是什么 Stroustrup所说的

在C中,您可以将void *隐式转换为T *。这不安全

然后,他继续展示一个示例,说明void *如何造成危险并说:

...因此,在C ++中,要从void *获取T *,您需要进行显式转换。...

最后,他指出:

在C中这种不安全转换的最常见用途之一是将malloc()的结果分配给合适的指针。例如:

int * p = malloc(sizeof(int));

在C ++中,使用typesafe new运算符:

int * p =新的int;

他在《 C ++的设计和演变》中对此进行了更详细的介绍。

因此答案可以归结为:语言设计师认为这是不安全的模式,因此将其定为非法,并提供了完成该模式通常使用的替代方法。


1
关于该malloc示例,值得注意的是malloc(显然)不会调用构造函数,因此,如果它是要为其分配内存的类类型,则能够隐式转换为该类类型将产生误导。
chbaker0 2015年

这是一个很好的答案,可以说比我的更好。我只是有点不喜欢“权威论证”的方法。
Mike Nakis

“ new int”-哇,作为C ++新手,我在想一种将基本类型添加到堆的方法时遇到了麻烦,我什至不知道您可以做到这一点。-1用于malloc。
Katana314 2015年

3
@MikeNakisFWIW,我的目的是补充您的答案。在这种情况下,问题是“他们为什么这样做”,所以我认为从首席设计师那里得到的消息是合理的。

11

在C语言中,无需将void *强制转换为任何其他指针类型,它总是可以安全地进行提升。

一直都在推广,是的,但并不安全

C ++禁用此行为的原因恰恰是因为它试图拥有比C更安全的类型系统,并且此行为并不安全。


通常考虑以下三种类型转换方法:

  1. 强制用户将所有内容写出,因此所有转换都是明确的
  2. 假设用户知道他们在做什么,并将任何类型转换为其他类型
  3. 使用类型安全的泛型(模板,new-expressions),显式的用户定义的转换运算符来实现完整的类型系统,并仅强制执行编译器看不到的隐式安全的事情来强制进行显式转换

好吧,1丑陋,是完成任何事情的实际障碍,但可以真正用于需要非常小心的地方。C大致选择了2,这更容易实现,而C ++选择了3,这更难实现但更安全。


达到3是一条漫长的艰辛之路,以后会添加例外,以后还会添加模板。
JDugugz

嗯,这样一个完整的安全类型系统实际上并不需要模板:基于Hindley-Milner的语言在没有它们的情况下相处得很好,根本没有隐式转换,也不需要显式地写出类型。(当然,这些语言依赖于类型擦除/垃圾回收/更高种类的多态性,而C ++避免了这种情况。)
2015年

1

根据定义,void指针可以指向任何东西。任何指针都可以转换为空指针,因此,您将能够转换回完全相同的值。但是,指向其他类型的指针可能具有约束,例如对齐约束。例如,设想一种架构,其中字符可以占用任何内存地址,但整数必须在偶数地址边界处开始。在某些体系结构中,整数指针甚至可能一次计数16、32或64位,因此char *在指向内存中的同一位置时实际上可能具有int *的数值的倍数。在这种情况下,从void *转换实际上会舍入无法恢复的位,因此不可逆。

简而言之,void指针可以指向任何东西,包括其他指针可能无法指向的东西。因此,向void指针的转换是安全的,但并非相反。

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.