24 April 2023

C++ 核心指南目录

“If you must use a cast, use a named cast”

理由

可读性更好。可避免错误。比 C 风格或函数风格的转换更准确。可以让编译器捕捉某些错误。

带名字的类型转换有:

  • static_cast
  • const_cast
  • reinterpret_cast
  • dynamic_cast
  • std::movemove(x)是一种从右值引用转为 x 的操作
  • std::forwardforward<T>(x)是一种依赖于类型 T 从右值或左值引用转成 x 的操作
  • gsl::narrow_castnarrow_cast<T>(x)是一种 static_cast<T>(x)
  • gsl::narrownarrow<T>(x)是静态类型转换static_cast<T>(x),并且要么static_cast<T>(x) == x, 要么会抛出异常 narrowing_error

例子

class B { /* ... */ };
class D { /* ... */ };

template<typename D> D* upcast(B* pb)
{
    D* pd0 = pb;                        // error: no implicit
                                        // conversion from B* to D*
    D* pd1 = (D*)pb;                    // legal, but what is done?
    D* pd2 = static_cast<D*>(pb);       // error: D is not derived from B
    D* pd3 = reinterpret_cast<D*>(pb);  // OK: on your head be it!
    D* pd4 = dynamic_cast<D*>(pb);      // OK: return nullptr
    // ...
}

以上是来自真实世界的 bug , D一开始是从 B 派生的,后来某个人重构了代码, D 不再是 B 的子类。 C 风格的强制类型转换是危险的,因为可以做任何类型转换,剥夺了任何防止错误的保护措施。

注意

如果类型转换不会导致丢失信息(比如从 float 转为 double ,从 int32 转为 int64 )可以考虑用花括号初始化。

double d {some_float};
int64_t i {some_int32};

这样写,可以表明是类型转换是有意为之。并且避免了可能导致精度丢失的转换。(如果是从 double 转为 float ,编译其会报错)。

double x = 10.1;
float f{x};
C-src-Qi3AYa.cpp: In function 'int main()':
C-src-Qi3AYa.cpp:12:9: warning: narrowing conversion of 'x' from 'double' to 'float' [-Wnarrowing]
   12 | float f{x};
      |         ^

注意

reinterpret_cast 有时候是需要的,(比如,把机器地址转为指针),但是它并不类型安全。

auto p = reinterpret_cast<Device_register>(0x800);  // inherently dangerous

强化

  • 标注所有 C 风格的强制类型转换,包括转到 void
  • 标注函数风格的强制类型转换Type(value)。使用花括号Type{value},不会导致类型变窄。
  • 类型转换指南集禁用 reinterpret_cast
  • 类型转换指南集警告针对数值类型使用 static_cast