为啥使用 static_cast<int>(x) 而不是 (int)x?

Posted

技术标签:

【中文标题】为啥使用 static_cast<int>(x) 而不是 (int)x?【英文标题】:Why use static_cast<int>(x) instead of (int)x?为什么使用 static_cast<int>(x) 而不是 (int)x? 【发布时间】:2010-09-11 08:05:19 【问题描述】:

我听说static_cast 函数应该优先于 C 风格或简单的函数风格转换。这是真的?为什么?

【问题讨论】:

反对你的荣誉,asked and answered。 我不同意,另一个问题是关于描述 C++ 中引入的演员表之间的差异。这个问题是关于static_cast的真正用处,略有不同。 我们当然可以合并这两个问题,但是我们需要从这个线程中保留的是使用函数而不是 C 样式转换的优势,目前仅在单行答案中提到在另一个线程中,没有投票。 这个问题是关于“内置”类型的,比如 int,而这个问题是关于类类型的。这似乎是一个足够显着的差异,值得单独解释。 static_cast 实际上是一个运算符,而不是一个函数。 【参考方案1】:

主要原因是经典的 C 类型转换在我们所说的 static_cast&lt;&gt;()reinterpret_cast&lt;&gt;()const_cast&lt;&gt;()dynamic_cast&lt;&gt;() 之间没有区别。这四件事完全不同。

static_cast&lt;&gt;() 通常是安全的。语言中有一个有效的转换,或者一个适当的构造函数使它成为可能。唯一有点冒险的时候是当你放弃继承的类时;您必须通过语言外部的方式(如对象中的标志)确保该对象实际上是您声称它的后代。只要检查结果(指针)或考虑可能的异常(参考),dynamic_cast&lt;&gt;() 就是安全的。

另一方面,reinterpret_cast&lt;&gt;()(或const_cast&lt;&gt;())总是危险的。你告诉编译器:“相信我:我知道这看起来不像foo(这看起来好像它不是可变的),但它是”。

第一个问题是,如果不查看大量分散的代码片段并了解所有规则,几乎不可能判断出哪个会出现在 C 风格的转换中。

让我们假设这些:

class CDerivedClass : public CMyBase ...;
class CMyOtherStuff ... ;

CMyBase  *pSomething; // filled somewhere

现在,这两个编译方式相同:

CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked

pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
                                     // Safe; as long as we checked
                                     // but harder to read

但是,让我们看看这段几乎相同的代码:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert

pOther = (CMyOtherStuff*)(pSomething);            // No compiler error.
                                                  // Same as reinterpret_cast<>
                                                  // and it's wrong!!!

如您所见,如果不了解所涉及的所有类,就很难区分这两种情况。

第二个问题是 C 风格的转换太难定位了。在复杂的表达式中,很难看到 C 风格的强制转换。如果没有成熟的 C++ 编译器前端,几乎不可能编写需要定位 C 风格转换的自动化工具(例如搜索工具)。另一方面,很容易搜索“static_cast

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
      // No compiler error.
      // but the presence of a reinterpret_cast<> is 
      // like a Siren with Red Flashing Lights in your code.
      // The mere typing of it should cause you to feel VERY uncomfortable.

这意味着,不仅 C 风格的强制转换更危险,而且很难找到它们以确保它们是正确的。

【讨论】:

您不应该使用static_cast 来分解继承层次结构,而应该使用dynamic_cast。这将返回空指针或有效指针。 @David Thornley:我同意,通常。我想我指出了在这种情况下使用static_cast 的注意事项。 dynamic_cast 可能更安全,但它并不总是最好的选择。有时您确实知道指针指向给定的子类型,这意味着编译器不透明,static_cast 更快。至少在某些环境中,dynamic_cast 需要可选的编译器支持和运行时成本(启用 RTTI),并且您可能不想仅仅为了您可以自己做的几项检查而启用它。 C++ 的 RTTI 只是该问题的一种可能解决方案。 您关于 C 演员表的说法是错误的。所有 C 强制转换都是值转换,大致相当于 C++ static_castreinterpret_cast 的 C 等效项是 *(destination_type *)&amp;,即获取对象的地址,将该地址转换为指向不同类型的指针,然后取消引用。除了 C 定义此构造行为的字符类型或某些结构类型外,它通常会导致 C 中未定义的行为。 您的好答案涉及帖子的正文。我正在寻找标题“为什么使用 static_cast(x) 而不是 (int)x”的答案。也就是说,对于int 类型(以及单独的int),为什么要使用static_cast&lt;int&gt;(int),因为唯一的好处似乎是类变量和指针。请您详细说明。 @chux, for int dynamic_cast 不适用,但所有其他原因都成立。例如:假设v 是一个声明为float 的函数参数,那么(int)v 就是static_cast&lt;int&gt;(v)。但是如果把参数改成float*(int)v就会悄悄变成reinterpret_cast&lt;int&gt;(v),而static_cast&lt;int&gt;(v)是非法的,被编译器正确捕获。【参考方案2】:

一个实用提示:如果您打算整理项目,可以在源代码中轻松搜索 static_cast 关键字。

【讨论】:

您也可以使用方括号进行搜索,例如“(int)”,但是很好的答案和使用 C++ 样式转换的正当理由。 @Mike 会发现误报 - 带有单个 int 参数的函数声明。 这可能会产生误报:如果您搜索的代码库不是唯一的作者,您将找不到其他人可能出于某些原因引入的 C 样式转换。 这样做对整理项目有何帮助? 您不会搜索 static_cast,因为它很可能是正确的。您希望在搜索 reinterpret_cast、const_cast 甚至可能是 dynamic_cast 时过滤掉 static_cast,因为这些表示可以重新设计的地方。 C-cast 混合在一起,并没有给你选择的理由。【参考方案3】:

简而言之

    static_cast&lt;&gt;() 为您提供编译时检查能力,C 风格 演员不会。 static_cast&lt;&gt;() 很容易被发现 C++ 源代码中的任何地方;相比之下,C_Style 演员更难发现。 使用 C++ 强制转换可以更好地传达意图。

更多解释

静态转换执行兼容类型之间的转换。它 类似于 C 风格的演员表,但更具限制性。例如, C 风格的转换将允许一个整数指针指向一个字符。

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

因为这会导致一个 4 字节指针指向已分配的 1 个字节 内存,写入此指针将导致运行时错误或 会覆盖一些相邻的内存。

*p = 5; // run-time error: stack corruption

与 C 风格的转换相比,静态转换将允许 编译器检查指针和指针数据类型是否为 兼容,这允许程序员捕捉到这个不正确的 编译期间的指针分配。

int *q = static_cast<int*>(&c); // compile-time error

阅读更多:What is the difference between static_cast<> and C style casting 和Regular cast vs. static_cast vs. dynamic_cast

【讨论】:

我不同意static_cast&lt;&gt;() 更具可读性。我的意思是,有时确实如此,但大多数时候——尤其是在基本整数类型上——它只是非常冗长且不必要。例如:这是一个交换 32 位字的字节的函数。使用static_cast&lt;uint##&gt;() 转换几乎不可能阅读,但使用(uint##) 转换很容易理解。 代码图片: imgur.com/NoHbGve @ToddLehman:谢谢,但我也没有说always。 (但大多数时候是的)在某些情况下,c 风格的演员表更具可读性。这就是 c 风格转换仍然在 c++ imho 中活跃和发挥作用的原因之一。 :) 顺便说一句,这是一个很好的例子 @ToddLehman 该图像中的代码使用两个链接的强制转换 ((uint32_t)(uint8_t)) 来实现除最低字节之外的字节被重置。为此,有按位和 (0xFF &amp;)。强制转换的使用混淆了意图。 static_cast 并非不可读,它只是冗长。就编译时行为而言,它也是明确的。只有让我们相信读者会在模棱两可但可读的代码中发现比编译器在编译语义明确的代码时发现的错误更多的错误,才能赢得可读性胜过清晰的论点。仅仅声称“thisthat 更具可读性”并不能赢得胜利——但如果这确实是唯一的争论点,我认为 static_cast 是处于领先地位,特别是因为明确性本身就是可读性的属性。 @ToddLehman 您的代码是一个确切的例子,说明了为什么首先要避免强制转换。 (参见没有强制转换的替代实现fxr.watson.org/fxr/source/lib/libkern/bswap32.c?v=NETBSD3)【参考方案4】:

问题不仅仅是使用 wither static_cast 或 C 风格转换,因为在使用 C 风格转换时会发生不同的事情。 C++ 强制转换运算符旨在使这些操作更加明确。

从表面上看,static_cast 和 C 风格的转换看起来是一样的,例如将一个值转换为另一个值时:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

这两个都将整数值转换为双精度值。然而,当使用指针时,事情变得更加复杂。一些例子:

class A ;
class B : public A ;

A* a = new B;
B* b = (B*)a;                                  //(1) what is this supposed to do?

char* c = (char*)new int( 5 );                 //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error

在这个例子中 (1) 可能没问题,因为 A 指向的对象实际上是 B 的一个实例。但是如果你在代码中不知道 a 实际指向的那个点怎么办? (2) 可能完全合法(您只想查看整数的一个字节),但也可能是错误,在这种情况下,错误会很好,例如 (3)。 C++ 强制转换运算符旨在通过在可能的情况下提供编译时或运行时错误来暴露代码中的这些问题。

因此,对于严格的“值转换”,您可以使用 static_cast。如果您想要指针的运行时多态转换,请使用 dynamic_cast。如果你真的想忘记类型,你可以使用 reintrepret_cast。把 const 扔出窗外,还有 const_cast。

他们只是让代码更明确,这样看起来你知道自己在做什么。

【讨论】:

【参考方案5】:

static_cast 表示你不能不小心const_castreinterpret_cast,这是好事。

【讨论】:

相对于 C 风格演员表的其他(虽然相当小)优势是它更加突出(做一些可能不好的事情应该看起来很丑)并且它更适合 grep。 在我看来,grep 能力总是一个加分项。【参考方案6】:
    允许在 您的代码使用 grep 或类似的 工具。 明确说明什么类型 你正在做的演员和参与 编译器在执行它方面的帮助。 如果你只想抛弃 const-ness,那么你可以使用 const_cast,它不允许你 进行其他类型的转换。 演员阵容本身就是丑陋的——你作为 程序员正在否决如何 编译器通常会对待你的 代码。你说的是 编译器,“我比你更清楚。” 既然如此,那就说得通了 表演演员应该是 中度痛苦的事情,和 他们应该在你的 代码,因为它们是可能的来源 的问题。

见Effective C++简介

【讨论】:

对于类,我完全同意这一点,但是对 POD 类型使用 C++ 样式转换有意义吗? 我想是的。所有 3 个原因都适用于 POD,并且只有一个规则而不是针对类和 POD 的单独规则会很有帮助。 有趣的是,我可能需要修改我在未来代码中为 POD 类型进行转换的方式。【参考方案7】:

这是关于你想强加多少类型安全。

当您编写 (bar) foo(如果您没有提供类型转换运算符,则相当于 reinterpret_cast&lt;bar&gt; foo)时,您是在告诉编译器忽略类型安全,并按照它的指示去做。

当您编写static_cast&lt;bar&gt; foo 时,您要求编译器至少检查类型转换是否有意义,并且对于整数类型,插入一些转换代码。


编辑 2014-02-26

我在 5 多年前写了这个答案,但我弄错了。 (见 cmets。)但它仍然得到了支持!

【讨论】:

(bar)foo 不等同于 reinterpret_cast(foo)。 "(TYPE) expr" 的规则是它会选择合适的 C++ 风格转换来使用,其中可能包括 reinterpret_cast。 好点。 Euro Micelli 给出了这个问题的明确答案。 另外,它是static_cast&lt;bar&gt;(foo),带括号。 reinterpret_cast&lt;bar&gt;(foo) 也一样。【参考方案8】:

C 风格转换很容易在代码块中遗漏。 C++ 风格转换不仅是更好的实践;它们提供了更大程度的灵活性。

reinterpret_cast 允许整数到指针类型的转换,但如果误用可能是不安全的。

static_cast 为数字类型提供了良好的转换,例如从枚举到整数或整数到浮点数或您确信类型的任何数据类型。它不执行任何运行时检查。

另一方面,

dynamic_cast 将执行这些检查,标记任何不明确的分配或转换。它仅适用于指针和引用,并且会产生开销。

还有其他几个,但这些是您会遇到的主要问题。

【讨论】:

【参考方案9】:

static_cast,除了操作指向类的指针外,还可用于执行类中明确定义的转换,以及执行基本类型之间的标准转换:

double d = 3.14159265;
int    i = static_cast<int>(d);

【讨论】:

既然(int)d 更加简洁易读,为什么还要写static_cast&lt;int&gt;(d)? (我的意思是基本类型,而不是对象指针。) @gd1 — 为什么有人会将一致性置于可读性之上? (实际上是半认真的) @ToddLehman :我,考虑到仅仅因为某些类型对您来说有些特殊而对它们进行例外处理对我来说没有任何意义,而且我也不同意您关于可读性的概念。更短并不意味着更具可读性,正如我从您在另一条评论中发布的图片中看到的那样。 static_cast 是做出一种非常特殊的转换的明确而有意识的决定。因此,它增加了意图的清晰度。作为在代码审查、错误或升级练习中搜索转换的源文件的标记,它也非常方便。 @ToddLehman 反驳:当intd 更具可读性时,为什么会有人写(int)d?构造函数,或者类似函数,如果你有(),语法并没有那么快演变成复杂表达式中括号的噩梦迷宫。在这种情况下,它将是 int id 而不是 int i = (int)d。更好的海事组织。也就是说,当我只需要一个临时表达式时,我使用 static_cast 并且从未使用过构造函数强制转换,我不认为。我只用(C)casts在匆忙写debug的时候couts...

以上是关于为啥使用 static_cast<int>(x) 而不是 (int)x?的主要内容,如果未能解决你的问题,请参考以下文章

为啥减法与static_cast溢出?

哪个演员更快 static_cast<int> () 或 int()

static_cast

为啥 static_cast 需要指针或引用?

Clang 不允许 static_cast 到带有模板的父类,而 g++ 和 icc 允许

如何在C ++(DirectX图形)中使用DrawLine