不要使用静态强制转换进行算术转换(cpp-core-guidelines)

Don't use static cast for arithmetic conversions (cpp-core-guidelines)

本文关键字:转换 cpp-core-guidelines 换进 静态      更新时间:2023-10-16

msvc 的 CPP 核心指南代码分析器告诉我

警告 C26472 不要将static_cast用于算术转换。用 大括号初始化,gsl::narrow_castgsl::narrow(类型.1(。

对于此代码段

static_cast<IntType>(static_cast<unsigned long long>(hexValue(digit)) << (digitIdx * 4));

为什么我不应该在这里使用static_cast?

此外,使用大括号 init 这看起来像这样

IntType{unsigned long long{hexValue(digit)} << (digitIdx * 4)};

这看起来也好不到哪里去。这看起来更像是函数样式转换,而不是其他任何东西。

我不能使用 gsl,我认为gsl::narrowstatic_cast本身的包装器,所以这纯粹是一个可读性问题吗?

那么这纯粹是一个可读性问题吗?

不。大括号初始化禁止缩小转换范围,并将导致诊断。该指南的目的是帮助保护代码免受意外缩小。static_cast将允许以静默方式缩小转换范围。

您似乎正在处理整数,因此如果不使用大于unsigned long long的扩展整数类型,您可能不会遇到任何缩小。

但是准则是针对一般情况的,即使没有实际风险,最好始终如一地编写代码。

以下是Microsoft文档的链接:

https://learn.microsoft.com/en-us/cpp/code-quality/c26472?view=vs-2019

  • gsl::narrow可确保无损转换,如果不可能,则会抛出gsl::narrowing_error
  • gsl::narrow_cast明确指出,转换可能会丢失数据,这是可以接受的。

static_cast不会执行这些检查,因此明确说明您的意图会更安全。

警告 C26472:不要使用 static_cast 进行算术转换。使用大括号初始化、gsl::narrow_castgsl::narrow(类型 1(。

建议"不要将static_cast用于算术类型"是避免强制转换的严格版本,更喜欢命名转换(在type.1中说(。

我的建议是,为了严格保持类型安全,static_cast不应该用于算术转换。但是,在许多情况下,不安全类型转换的几率非常低,或者可以通过算法和程序很好地确保类型安全性,static_cast可用于算术转换。


关于算术类型的类型安全性的更多思考如下:

大括号初始化是一种理想的类型安全策略,因为它禁止缩小转换范围。对于gsl::narrowgsl::narrow_cast,目前C++标准库中没有类似的窄转换函数。

  • gsl::narrow确保无损转换,如果不可能,则抛出 gsl::narrowing_error。
  • gsl::narrow_cast明确指出,转换可能会丢失数据,这是可以接受的。

根据GSL的文件,gsl::narrow_cast是与static_cast相同的命名演员。 因此,它仍然会导致不安全的窄转换,因为它不检查它正在转换的值(与static_cast相同(。例如

std::uint8_t c1 = gsl::narrow_cast<std::uint8_t>(1000.5);// same as using static_cast
std::uint8_t c2 = gsl::narrow_cast<std::uint8_t>(-1.5); // same as using static_cast

上面的代码可以成功运行,而窄转换仍然不安全。 如果您不想使用 GSL 并且不太关心性能,也可以通过编写更简单的类型安全numeric_cast函数来实现算术转换,如以下代码中的 give 所示。

#include <iostream>
#include <limits>
void error(const std::string& errormessage){
std::cerr << errormessage;
throw std::runtime_error(errormessage);
}
template <typename Target, typename Source>
constexpr Target numeric_cast(Source&& src){    
if (typeid(Target) != typeid(bool) &&
(src < std::numeric_limits<Target>::lowest() ||
src > std::numeric_limits<Target>::max())  )
error("Unsafe numerical cast.n");
return static_cast<Target>(std::forward<Source>(src));
}
int main() {    
//1. Numerical promotion
float f1{ 1.1f };   double dd1{ f1 }; //Suggested!
double d1{ numeric_cast<double>(f1) }; //Ok

//2. Numerical narrowing
double dd2{ 1.1 }; float f2 = numeric_cast<float>(dd2); //OK 
std::size_t uu2{ 10 }; std::uint8_t u2 = numeric_cast<std::uint8_t>(uu2);  //OK 
//3. Numerical truncating
double d3{ 1.5 }; std::uint8_t u3 = numeric_cast<std::uint8_t>(d3);  //OK 
// Errors detecked
//double d4{ -1.5 }; std::uint8_t u4 = numeric_cast<std::uint8_t>(d4); //error detecked
//double dd4{ 1000 }; std::uint8_t uu4 = numeric_cast<std::uint8_t>(dd4); //error detecked   
}

numeric_cast函数可同时用于数值提升、数值缩小和数值截断。 它还可以通过检查正在转换的值和转换目标的限制来发现不合理的算术转换的错误。

当然,为了保持典型安全,与其他没有值检查的算术转换相比,numeric_cast的性能相对较低。