23 April 2023

C++ 核心指南目录

“Avoid casts”

理由

类型转换容易导致错误、导致代码优化不可靠。

坏例子

double d = 2;
auto p = (long*)&d;
auto q = (long long*)&d;
cout << d << ' ' << *p << ' ' << *q << '\n';
2 0 4611686018427387904

不同实现,以上代码的输出也不一样。

如果中间修改,结果更加奇怪:

double d = 2;
auto p = (long*)&d;
auto q = (long long*)&d;
*q = 666;
cout << d << ' ' << *p << ' ' << *q << '\n';
3.29048e-321 666 666

很奇怪吧,好在还没有导致程序崩溃。

注意

程序员用上类型转换,往往都觉得这是自己想要做的,觉得这样写代码更容易读。事实上,他们违背了使用数值的通用规则。重载解析或者模板实例化往往能够帮你选择合适的函数。如果没有重载或模板实例化,可能需要去实现这些,而非进行局部修理(类型转换)。

注意

对于系统开发语言来说,类型转换也是必不可少的。比如,除了类型转换,还有什么把设备寄存器的地址存入指针?但是,总的来说,类型转换的使用还是严重过度了。是代码错误很主要的来源。

如果你感觉需要很多类型转换,那么,你肯定遇到很根本的设计问题。

类型规则集禁止使用reinterpret_cast和 C 风格的类型转换。

不要把[[nodiscard]]返回值强制转换成 void 。如果你确实有意要抹掉返回值?如果代码评审和你都觉得忽略返回值是需要的。那么可以使用std::ignore =来消除编译器警告。这个方式更简单、可移植,也更容易通过 grep 搜索到。

[[nodiscard]] int get_value() { return 42; }
int main()
{
    std::ignore = get_value(); // no warning now: warning: ignoring
                               // return value of 'int get_value()',
                               // declared with attribute 'nodiscard'
                               // [-Wunused-result]
    return EXIT_SUCCESS;
}

强制类型转换用的优点过度。现代C++提供规则和机制避免在大部分情况下使用强制类型转换。比如:

  • 使用模板
  • 使用 std::variant
  • 使用定义良好的、安全、隐式指针类型转换
  • 使用std::ignore =忽略[[nodiscard]]返回值

强化

  • 标记 C 风格的强制类型转换,包括转换成 void 的情况
  • 标记函数形式的强制类型转换Type(value)。使用Type{value},这个方式可以避免变窄转换。
  • 标记源和目标类型一样的指针类型转换
  • 标记可以隐式转换的显示的指针类型转换