C++风格转换对性能的影响?

74
我对C++风格的转换不熟悉,我担心使用C++风格的转换会影响我的应用程序性能,因为我在中断服务例程中有一个实时关键期限
我听说有些转换甚至会抛出异常!
我想使用C++风格的转换,因为它会使我的代码更加"健壮"。然而,如果有任何性能损失,那么我可能不会使用C++风格的转换,而是会花更多时间测试使用C风格转换的代码。

有没有人进行过严格的测试/分析,以比较C++风格的转换和C风格的转换的性能?

你的结果是什么?

你得出了什么结论?


你已经得到了两个准确的答案(如果我这么说的话)。 - anon
1
它们为什么不会拥有相同的性能呢?唯一具有额外运行时成本的是dynamic_cast,而您在C中无法执行该操作。 - Brian Neal
2
更新了我的回答。现在它引用了标准中的章节和节。正如Neil所说,它们确实做了同样的事情。 - jalf
@snemarch:在这个语句中添加“在编译步骤期间禁用安全性”。产生相同的代码。static_cast只是挑剔它是否会导致编译器错误。 - jmucchiello
1
@jmucchiello:虽然这是一个老问题,但我偶然发现它并想为未来的读者澄清一下:static_castreinterpret_cast不会总是产生相同的运行时代码,并且实际上两种转换之间有非常少的重叠。例如:您不能使用reinterpret_cast将浮点数转换为整数(但可以从double*转换为int*),当您使用static_cast进行此操作时,它不是免费的,而实际上需要执行一个或多个指令(就像C风格的转换一样)。在类的情况下,static_cast甚至可能调用函数。 - MikeMB
显示剩余5条评论
7个回答

102
如果 C++ 风格的强制类型转换可以通过 C 风格的强制类型转换来概念上替代,那么不会有开销。如果无法这样做,例如在 dynamic_cast 的情况下,因为没有 C 的等价方式,你必须付出某种代价。
作为一个例子,以下代码:
int x;
float f = 123.456;

x = (int) f;
x = static_cast<int>(f);

生成相同的代码 - 无论是使用VC++进行强制转换还是显式转换,代码如下:
00401041   fld         dword ptr [ebp-8]
00401044   call        __ftol (0040110c)
00401049   mov         dword ptr [ebp-4],eax

在C++中,唯一可能会抛出异常的转换操作是使用dynamic_cast将一个对象转换成引用类型。为了避免抛出异常,可以将对象转换成指针类型,如果转换失败将返回0。


3
C++风格的类型转换永远不会被C语言风格的类型转换所代替。如果有变化,那就是相反的情况。标准只规定了C++风格的类型转换的行为,而C语言风格的类型转换则是用C++风格的类型转换来描述的。 - jalf
1
@jalf 是的 - 我的意思是在概念上替换。 - anon
2
是的,但如果楼主要接受的话,最好再明确清晰一些。;) - jalf
21
但是汇编代码清单... 汇编代码清单!!!他想要什么,血吗? :) - Daniel Earwicker
1
很棒的答案,概念上的比较非常准确。 - Matt Joiner

48

运行时唯一需要额外成本的是dynamic_cast,因为它具有无法直接使用C风格转换实现的功能。所以你没有问题。

让自己放心的最简单方法是指示编译器生成汇编代码,并检查它所生成的代码。例如,在任何正常实现的编译器中,reinterpret_cast将完全消失,因为它只是意味着“盲目地前进并假装数据是这种类型”。


21
为什么会有性能损失?它们执行的功能与 C 强制类型转换完全相同。唯一的区别在于,它们在编译时捕获更多的错误,并且在源代码中更容易搜索。 static_cast<float>(3) 等效于 (float)3,并且将生成完全相同的代码。
考虑到一个 float f = 42.0freinterpret_cast<int*>(&f) 等效于 (int*)&f,并且将生成完全相同的代码。
只有 dynamic_cast 不同,它可以抛出异常。但这是因为它执行了 C 式强制类型转换不能做的事情。所以不要使用 dynamic_cast,除非你需要它的功能。
通常可以安全地假设编译器的作者是聪明的。如果两个具有相同语义的不同表达式,通常可以安全地假设它们将在编译器中实现相同。
糟糕啊:第二个例子当然应该是 reinterpret_cast,而不是 dynamic_cast。现在修正过来了。
好的,为了让它绝对清楚,这里是 C++ 标准说的:
§5.4.5:
引用: - 使用显式类型转换的强制类型转换符号可以执行以下转换: - const_cast (5.2.11) - static_cast (5.2.9) - static_cast,后跟 const_cast - reinterpret_cast (5.2.10),或 - reinterpret_cast 后跟 const_cast。 相同的语义限制和行为适用。如果一个转换可以由多个不同的符号表示,则使用一个符号引起的行为与使用另一个符号引起的行为相同。如果可以用上述方式之一对其进行多种解释,则将列表中出现的第一个解释用于强制转换,即使由此得到的转换不合法。

因此,如果说有什么区别的话,由于C风格的强制转换是基于C++强制转换实现的,所以C风格的转换应该会更慢一些。(当然,它们并不会慢,因为编译器在任何情况下都会生成相同的代码,但这比C++风格的强制转换较慢更可信。)


1
鉴于您的评论:“通常可以安全地假设编译器中它们将被实现为相同的方式”,您是否已经或曾经从事过嵌入式工作或实时关键性工作? - Trevor Boyd Smith
dynamic_cast 只会在操作引用时抛出异常。如果它在指针上工作,那么如果转换失败,它将返回0。而且你为什么要在 float* 到 int* 的转换中使用 dynamic_cast?这毫无意义。 - Brian Neal
3
当我读到这个问题时,我首先想到的是哈哈,5.4.5版本。 :D - Johannes Schaub - litb
哈哈,别害怕。我只是知道C风格的转换是基于C++风格的定义的 :p 我不喜欢记住那样的章节编号 :) - Johannes Schaub - litb
@litb:顺便问一下,你能为http://tinyurl.com/c9daz8 提供一些线索吗?;) - jalf
显示剩余2条评论

19

有四种C ++强制类型转换:

  • const_cast
  • static_cast
  • reinterpret_cast
  • dynamic_cast

如前所述,前三个是编译时操作。使用它们不会产生运行时惩罚。这是向编译器发送消息,表明已声明的数据需要以不同的方式访问。“我说这是一个int*,但让我像指向sizeof(int)charchar*一样访问它”,或者“我说这些数据是只读的,现在我需要将其传递给一个不修改它,但不以const引用作为参数的函数。”

除了通过将类型进行错误的强制类型转换并在数据上覆盖而导致数据损坏(这始终是C风格转换的可能性)之外,这些转换最常见的运行时问题是实际上已声明为const的数据可能无法强制转换为非常量。将声明为const的内容转换为非const然后进行修改是未定义的。未定义意味着您甚至不能保证会崩溃

dynamic_cast是运行时构造,因此必须有运行时成本。

这些转换的价值在于它们明确说明您要从哪里/到哪里进行强制类型转换,外观醒目,并且可以使用智障工具进行搜索。我建议使用它们而不是使用C风格的转换。


3
实际上,static_cast 也可以导致运行时操作,就像 C 风格的转换一样(例如,当您将浮点数转换为整数或反之亦然时)。 - MikeMB
1
为什么要使用 static_cast 在浮点数和整数之间进行转换?你可以直接赋值:double d = 5; int i = d;。转换可能会带来运行时成本,但即使您不编写 static_cast,也必须支付该成本。 - Max Lybbert
1
在某些情况下,不允许隐式的窄化转换(例如列表初始化),或者您希望将转换明确化。例如,选择特定的重载或记录(对您或编译器)正在进行的转换。此外,float到int的转换只是一个例子-想想类的显式转换运算符/构造函数或在层次结构中上/下强制转换指针,其中存在多继承。 - MikeMB
1
是的,它与 C 风格转换或隐式转换相比没有额外的开销。我想要明确说明的是,static_cast 做的不仅仅是告诉编译器以不同的方式解释位(这就是 reinterpret_castconst_cast 所做的)。 - MikeMB

4
使用dynamic_cast时,运行时会进行多项检查以防止您做出愚蠢的事情(更多信息请参见GCC邮件列表),一个dynamic_cast的成本取决于受影响的类有多少个,受影响的是哪些类等等。
如果您确信转换是安全的,仍然可以使用reinterpret_cast

如果您发现需要使用reinterpret_cast,则肯定已经进入了“实现特定”领域,可能还会涉及到“未定义行为”的领域。 - anon
请注意,reinterpret_cast 可能受到不同的别名规则限制,与 static_cast 等不同。 - leander

3
尽管我同意“在运行时唯一具有额外成本的是dynamic_cast”这个说法,但请记住可能存在特定于编译器的差异。
我看到了针对我的当前编译器提交的一些错误报告,其中代码生成或优化因使用C-style与C++-stylestatic_cast转换而略有不同。
因此,如果您担心,请检查热点上的反汇编。否则,当您不需要它们时,请避免使用动态转换。(如果关闭RTTI,则无法使用dynamic_cast)。

我很想看到一个在不需要使用 dynamic_cast 时的示例。 - anon
1
一个例子是,如果你向下转换到一个派生类(所以你经常使用dynamic_cast),但你已经知道这个转换是合法的,那么你可以使用static_cast。 - jalf
1
@Neil:我想我的意思更多是“重构以避免使用dynamic_cast”。在我开始在继承层次结构中添加“virtual”之前,我会仔细考虑后果,因为我知道我正在做出权衡,并且有时选择不同的设计模式可以解决问题... - leander
@Neil:如果你确定这是一个安全的向下转换,并且没有虚拟继承,那么可以使用static_cast。此外,我相信你可以使用dynamic_cast进行向上转换,但这通常是愚蠢的(也许除了虚拟继承?)。这里涉及到较少人知道的领域... - leander
我看到了一些针对我的当前编译器的错误报告,其中代码生成或优化在使用C风格与C++风格的static_cast转换时略有不同。我想知道C风格转换是否做了多于一件事(例如const_cast和static_cast)。 - Max Lybbert

2

规范的真相是汇编,所以尝试两种方法并查看是否得到不同的逻辑。

如果你得到完全相同的汇编代码,那么就没有区别- 不可能有区别。你唯一需要坚持使用旧的C强制转换的地方是在纯C例程和库中,在这些情况下引入C++依赖关系是没有意义的。

需要注意的一件事是,在一个相当大的代码片段中,强制转换随处可见。在我整个职业生涯中,我从来没有搜索过“所有强制转换”的逻辑- 你倾向于搜索特定类型的强制转换,比如'A',而"(A)"的搜索通常与"static_cast<A>"之类的内容一样有效。使用新的强制转换进行类型验证等操作,而不是因为它们使你永远不会执行的搜索更容易。


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