使用C风格的强制类型转换对于内置类型是否可行?

17
在阅读了许多关于C++中C风格转换的答案后,我仍有一个小问题。我能否对内置类型使用C风格转换,如long x = (long)y;,或者它仍被认为是不好和危险的?
9个回答

29
我可以对内置类型使用类C的强制转换,例如long x =(long)y;还是仍被认为是不好和危险的? 不要使用它们。反对使用它们的原因在这里也适用。基本上,一旦使用它们,所有的赌注都无法确定,因为编译器将不再帮助您。虽然这对指针比其他类型更加危险,但仍然可能是危险的,并且在错误的情况下提供较差的编译器诊断,而新样式的转换提供了更丰富的错误消息,因为它们的使用更有限:Meyers引用了去除constness的例子:使用除const_cast之外的任何cast都无法编译,从而清楚地说明发生了什么。 或者说,无论类型如何,还存在一些其他缺点,即语法方面的考虑:C样式转换非常不显眼。这不是好事:C ++转换在代码中非常明显,指向潜在危险的代码。它们也可以很容易地在IDE和文本编辑器中搜索。尝试在大型代码中搜索C-样式转换,您会看到这有多难。另一方面,C样式转换与C ++样式转换相比没有任何优势,因此甚至没有需要考虑的权衡。更一般地,Scott Meyers建议在Effective C ++(项目27)中“最小化转换”,因为“转换破坏了类型系统”。

1
在最小化强制转换方面是正确的。但是你能指出不执行float y = 1.0; double x = double(y)的任何理由吗?因为*那才是问题所在。这种强制转换并不危险,与真正危险的强制转换相比(如果你正确使用则几乎不需要),它可能不应该分散注意力。 - ypnos
3
在那种特定情况下,由于double比float类型更大,因此甚至不需要使用转换。 - Raphaël Saint-Pierre
1
“casts subvert the type system”听起来相当奇怪。我不会说像int convertDoubleToInt(double)这样的函数颠覆了类型系统。但这基本上就是static_cast<int>(myDouble)所做的。真正颠覆类型系统的是const_castreinterpret_cast,而不是另外两个转换。 - Ruslan
@Ruslan 我引用了Scott Meyers的话。确实,特定情况下的static_cast<int>(some_double)并不会破坏类型系统。但是,一般情况下的static_cast可以破坏类型系统,例如将void*转换为T*(这会破坏类型系统,因为它不执行值转换;相反,它断言了类型系统无法验证的内容)。对于转换,我强烈建议使用T{obj}语法,因为它使意图更清晰。不过,这也不是绝对可靠的(std::vector<…>{}不执行转换...)。 - Konrad Rudolph
@Ruslan 不好意思,这是我的疏忽。对于非缩小转换,我仍然推荐使用T{}。缩小转换可能需要使用static_cast来突出显示它们。 - Konrad Rudolph
显示剩余5条评论

19
我不会这样做,原因如下:
  • 强制类型转换在代码中应该是丑陋的,需要显眼并可以使用grep和类似的工具进行查找。
  • "始终使用C++强制类型转换"是一个简单的规则,比起 "对于用户定义的类型使用C ++强制类型转换,但是在内置类型上使用C风格的强制类型转换是可以的" 更容易记住和遵循。
  • C ++风格的强制类型转换向其他开发人员提供了更多关于为什么需要进行转换的信息。
  • C风格的强制类型转换可能让您进行您没有意图的转换 - 如果您有一个接受(int*)的接口,并且您正在使用C风格的强制类型转换来传递const int*,如果接口更改为接受long*,您使用C风格的强制类型转换的代码将继续工作,即使这不是您想要的。

3
我不相信这些理由中的任何一个(除了第一个可能有争议的原因)适用于数字类型之间的转换;请参见我的答案。 - zwol

11
如果您要将数字类型从一种类型转换为另一种类型,那么我认为C风格的强制类型转换比`*_cast`操作符更好。每个`*_cast`操作符都有特定的原因不适用于数字类型:
- `reinterpret_cast`应用于数字类型时,执行的是正常的数字转换,而不是重新解释位,即编写 `reinterpret_cast(3.14159)` 不会生成与该浮点常量具有相同位表示的整数,这与直觉相反。 - `const_cast`在数字值上从未必要,如果应用于数字变量(而不是指向数字的指针或引用),则表示该变量的类型不正确。 - 数字永远没有动态类型,因此`dynamic_cast`在数字上根本没有意义。 - `static_cast`通常用于类类型。因此,将`static_cast`应用于数字会看起来很奇怪;读者会想知道`static_cast`是否与C风格的强制类型转换在适用于数字时存在不同之处。(实际上它们是相同的,但我不得不多次阅读相关的C ++规范部分才确信它们是相同的,我仍然对此有“这里可能有些奇怪”的反应。)
此外,还有一个避免使用它们的风格上的原因:
- 如果需要在数字类型之间进行强制类型转换,则可能需要在一组中执行多个此操作;因此,C风格的强制类型转换的简洁性非常重要。例如,我正在编写的程序可以有很多此类操作:
uint32_t n = ((uint32_t(buf[0]) << 24) |
              (uint32_t(buf[1]) << 16) |
              (uint32_t(buf[2]) <<  8) |
              (uint32_t(buf[3])      ));

在这里使用static_cast会掩盖算术操作,而这才是重要的事情。(注意:如果sizeof(uint32_t) <= sizeof(unsigned int),这些转换是不必要的,并且现在已经是一个安全的假设,但我仍然更喜欢显式。)(是的,我可能应该将此操作分解为be32_to_cpu内联助手,但我仍会以相同的方式编写代码。)


你声称 static_cast 和 C 风格的转换是相同的是错误的。一个 C 风格的转换可以根据情况解释为 reinterpret_cast,const_cast 或者 static_cast。 - PeterSW
5
C语言风格的强制类型转换与static_cast在数值类型之间的转换方面是相同的。 - zwol
1
如果您将类型转换为可以容纳指针的整数类型,那么使用 static_cast 可以避免意外的 reinterpret_cast 隐藏为 C 风格的转换。如果您要转换的类型是您认为是整数类型的指针,则会发生这种情况。嗯,在我们开始这个讨论之前,我感觉我有点在抓住稻草...现在我想,我刚刚描述的情况是唯一的情况,而不是更多潜在的程序员错误可以在转换为数字类型时被捕获。 - PeterSW
5
只有在源和目标都明确为数字类型的情况下,我的建议才适用。如果可能涉及到指针,我同意使用新式转换运算符更安全。 - zwol
2
"static_cast 通常用于类类型。因此,在数字上应用 static_cast 看起来很奇怪",这听起来像是个人期望,似乎没有任何事实依据。 - François Andrieux

3

我能想到一种合法使用C风格转换的情况:

// cast away return value to shut up pedantic compiler warnings
(void)printf("foo\n");

3

在我看来,在使用标准库函数和STL时,对于内置类型进行C风格转换是可以的,并且能够使代码更易读。

在我工作的公司中,我们使用最高级别(4级)警告来编译代码,因此我们会收到有关每个小型类型转换的警告......所以我在这些情况下使用C风格转换,因为它们更简短、不那么冗长并且很有意义。

for (int i = 0; i < (int)myvec.size(); i++)
{
  // do something int-related with i
}
float val = (float)atof(input_string);

但是,如果它是在可能会更改的代码(例如库)中,则使用static_cast<>()更好,因为您可以确保编译器在类型更改且转换不再有意义时出错。此外,如果只使用C样式,那么在代码中搜索转换是不可能的。 " static_cast<mytype>(" 很容易搜索。 :)


2

考虑到上下文,我认为这可能是可以的。例如:

/* Convert -1..1 to -32768..32767 */
short float_to_short(float f)
{
    return (short)(max(-32768.f, min(32767.f, f * 32767.f)));
}

0
为什么你需要这种特定的转换?任何数字类型都可以在没有强制转换的情况下转换为long(可能会丢失精度),所以强制转换并不能让编译器做它本来做不到的事情。通过强制转换,你只是剥夺了编译器在存在潜在问题时发出警告的能力。如果你要将其他基本类型(比如指针)转换为long,我真的希望能看到reinterpret_cast<>而不是C类型转换,这样如果出现问题,我就可以轻松地找到原因。
我不会同意没有理由进行强制转换,当然也不会同意没有充分理由的C类型转换。我不认为在大多数内置类型之间进行转换有必要,如果确实有必要,我希望能通过文本搜索轻松找到原因。

如果您不希望编译器生成精度丢失错误,则需要进行强制类型转换。 - Michael Platings
当然可以,但是你现在最好抑制它们。当我们转向64位处理(需要更多的内存空间)时,如果我们进行强制类型转换,在某些情况下(涉及size_t),我们将失去精度。 - David Thornley

0

我更喜欢使用传统的C语言强制类型转换的情况是将字节缓冲区转换为不同的有符号性。许多API具有不同的约定,实际上并没有“正确答案”,在进行转换的上下文中,这种转换并不危险,因为代码只需要与使用的库组合一样跨平台。

我想给出一个具体的例子,我认为这样做很好:

unsigned char foo[16];
lib1_load_foo(key);
lib2_use_foo((char*)key);

但是对于其他任何情况,如果需要强制类型转换,它可能会产生潜在的副作用,这应该引起注意,并且使用C++风格(可以说是丑陋的)的强制类型转换是正确的选择。而如果不需要强制类型转换,则不要使用强制类型转换。


-1
如果你发现需要进行C风格的转换(或reinterpret_cast),那么请非常仔细地检查你的代码,99.99%的情况下,代码中肯定存在问题。这两种类型的转换几乎不可避免地会导致实现特定的(往往是未定义的)行为。

如果你欺骗编译器,它会惩罚你。这些强制转换要么是谎言,要么是试图修复之前的谎言。 - Darron
4
在研究代码中,我经常看到处理浮点数和双精度数的代码混合使用,或者例如将来自图像的整型数据转换为浮点数/双精度数以进行进一步处理。这些都是强制类型转换的真实用例,我认为它们的比例远超过0.01%。 - ypnos
在Windows上的线程函数中,参数怎么处理?你需要传递void*类型。常见的模式是传递reinterpret_cast<void*>(this)。 - Dave Mooney
3
类似这样的回答让我想知道人们以前是否编写过真正的代码。在实际的、实用的代码中,浮点类型之间的强制转换(即使您试图避免它们,因为它们非常慢),有符号和无符号整数之间的强制转换,整数和 vector::size() 返回的任何可怕类型之间的转换等等是绝对不可避免和完全正常的,尤其是当您需要将具有 API 的库粘合在一起时,而这些 API 是您无法控制的。 - user168715

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接