C++ 单参数构造函数采用 explicit 修饰,防止隐式转换

2018/09/25 C++ explicit修饰符

C++ 隐式类型转换

总述

隐式类型转换允许一个某种类型 (称作 源类型) 的对象被用于需要另一种类型 (称作 目的类型) 的位置, 例如, 将一个 int 类型的参数传递给需要 double 类型的函数.

除了语言所定义的隐式类型转换, 用户还可以通过在类定义中添加合适的成员定义自己需要的转换. 在源类型中定义隐式类型转换, 可以通过目的类型名的类型转换运算符实现 (例如 operator bool()). 在目的类型中定义隐式类型转换, 则通过以源类型作为其唯一参数 (或唯一无默认值的参数) 的构造函数实现.

explicit 关键字可以用于构造函数或 (在 C++11 引入) 类型转换运算符, 以保证只有当目的类型在调用点被显式写明时才能进行类型转换, 例如使用 cast. 这不仅作用于隐式类型转换, 还能作用于 C++11 的列表初始化语法:

class Foo
{
  explicit Foo(int x, double y);
    ...
};

void Func(Foo f);

此时下面的代码是不允许的:

Func({2, 1.989});  // Error

这一代码从技术上说并非隐式类型转换, 但是语言标准认为这是 explicit 应当限制的行为.

优点

  1. 有时目的类型名是一目了然的, 通过避免显式地写出类型名, 隐式类型转换可以让一个类型的可用性和表达性更强.

  2. 隐式类型转换可以简单地取代函数重载.

  3. 在初始化对象时, 列表初始化语法是一种简洁明了的写法.

缺点

  1. 隐式类型转换会隐藏类型不匹配的错误. 有时, 目的类型并不符合用户的期望, 甚至用户根本没有意识到发生了类型转换.

  2. 隐式类型转换会让代码难以阅读, 尤其是在有函数重载的时候, 因为这时很难判断到底是哪个函数被调用.
  3. 单参数构造函数有可能会被无意地用作隐式类型转换.
  4. 如果单参数构造函数没有加上 explicit 关键字, 读者无法判断这一函数究竟是要作为隐式类型转换, 还是作者忘了加上 explicit 标记.
  5. 并没有明确的方法用来判断哪个类应该提供类型转换, 这会使得代码变得含糊不清.
  6. 如果目的类型是隐式指定的, 那么列表初始化会出现和隐式类型转换一样的问题, 尤其是在列表中只有一个元素的时候.

结论

在类型定义中, 类型转换运算符和单参数构造函数都应当用 explicit 进行标记. 一个例外是, 拷贝和移动构造函数不应当被标记为 explicit, 因为它们并不执行类型转换. 对于设计目的就是用于对其他类型进行透明包装的类来说, 隐式类型转换有时是必要且合适的. 这时应当联系项目组长并说明特殊情况.

Search

    Table of Contents