在我们的大型C ++项目中,我们力争没有警告,但是人们对此常常很懒。我一直在修正的一件事是来自如下代码的警告:
sizex = sizey = 32 * c_scale;
给予
警告C4244:'=':从'double'转换为'i16',可能会丢失数据
其中sizex和sizey是i16类型,而c_scale是双精度类型。
所以我不断将这样的代码行更改为类似的
sizex = sizey = i16(32 * c_scale);
使警告消失。
我想说这会使代码的可读性降低,所以我对此并不满意,但我认为它比禁用警告要好,并且比让大量警告掩盖可能更严重的警告要好。 / p>
有人有更优雅或更不同的方式来处理这种情况吗?
答案 0 :(得分:3)
作为CppCoreGuidelines的一部分,有一个名为GSL的支持库。它们提供了一种安全的方式来转换称为 narrow_cast 的此类类型:
它的标题可以在这里找到:https://github.com/Microsoft/GSL/blob/master/include/gsl/gsl_util
auto sizex = gsl::narrow<unsigned>(32 * c_scale);
想法是,如果结果类型(此处为unsigned
)不够大(或违反符号),则会引发异常。
这是运行时检查。如果要避免运行时检查的开销,可以构造一个类似的函数,该函数仅在 debug 构建期间执行检查(当NDEBUG
未定义时),并删除对发布版本。
答案 1 :(得分:3)
关于可读性和可维护性的一些有用准则:
想象一下我加入了您的项目。我不知道i16
是什么,链接的任何库也不知道。但是我确实知道std::int16_t
是什么(在<cstdint>
中找到)。图书馆作家也是如此。
您在此处描述的操作是将整数按一定比例缩放(例如double?)。细节在于魔鬼。您不能在不丢失数据的情况下将双精度型转换回整数,因此必须恢复为强制转换,这看起来很杂乱,并使未来的维护人员想知道您在做什么。
所以让我们建立一个抽象:
inline
auto scale_and_round_down(std::int16_t value, double scale) -> std::uint16_t
{
auto scaled_value = value * scale; // answer will be a double
return std::int16_t(value * scale); // round down to nearest int
}
现在我们的代码变为:
sizex = sizey = scale_and_round_down(32, c_scale);
毫无疑问地表达了意图。在发行版本中,将内联scale_and_round_down
。您不会为这种抽象支付任何性能费用。
答案 2 :(得分:2)
我不知道您的i16
类型是什么,因为它不在C ++ 11标准中。我猜您是指ìnt16_t
中的<cstdint>
类型,因此是一个带符号的16位整数,并且您的sizex
和sizey
都是int16_t
类型。
您的c_scale
是一些double
。因此可能是IEEE754 64位双精度浮点数。有关更多信息,请参见http://floating-point-gui.de/。它有一个53位的尾数。
您如何期望16位中的53位适合无损失(在所有情况下)? pigeonhole principle适用并立即告诉您,由于2 53 大于2 16 ,因此并非如此。
编译器向您发出警告是正确的。确实,您可以明确强制转换以避免该警告
通过解释演员表,您只是告诉您的读者您已经知道这一点。不管有没有强制转换,生成的机器代码都不会改变。
也许您的特定编译器可能还有其他方法来禁用该警告(也许有些#pragma
)
您可以考虑使用一些更复杂的静态分析技术(也许是Frama-Clang)来进行语义分析,并向您证明强制转换不会泄漏精度。当心,这样的工具可能很难掌握!
顺便说一句,C ++标准在一般情况下对警告的内容不多(它们是“实现质量”的东西)。您可以决定忽略它们(但实际上不应这样做)。