STL 如何使用显式的?

本周在圣路易斯举行的 WG21 会议议程中有一篇论文P3116 “Policy for explicit(Zach Laine,2024 年)。“政策”的概念是 LEWG 希望为提案作者制定一个类似“风格指南”的东西。如果提案中noexcept位置错误、explicit[[nodiscard]]使用不当,能快速告知作者应如何修改,避免在每篇论文上都进行相同讨论。

P3116 的基本想法是列出 C++标准文档的当前“内部风格”(特别是与explicit关键字相关的),不是创新或开拓新领域,而是描述库条款目前的做法,以保持与这种内部风格的一致性。

作者自己对于行业代码库中explicit使用的指南是:默认情况下所有构造函数都应为explicit,非explicit构造函数用于特殊情况。

但标准库规范有 40 年历史,不能像新行业代码那样,如vector的迭代器对构造函数为非explicit会导致未定义行为,但已有大量代码依赖此类构造函数,不能轻易更改。

作者理解的 STL 的explicit内部风格(不适用于行业代码)如下:

  • 规则 0:偏离以下规则需有充分理由,“我喜欢”或“我的雇主这样做”不是好理由。
  • 规则 1:一般除operator bool外不应有operator X转换函数,有正当理由的偏差包括basic_string::operator basic_string_viewexplicit chrono::year::operator int
  • 规则 2:operator bool应始终为explicit,如unique_ptr::operator booloptional::operator bool,但bitset::reference::operator bool为非explicit
  • 规则 3:构造函数的适当explicit性主要取决于参数数量,单参数构造函数应为explicit,多参数构造函数应为非explicit,若单个重载参数可变需拆分重载。
  • 规则 4:初始化列表构造函数、复制构造函数和移动构造函数应为非explicit,其他单参数构造函数应为explicit,但有正当理由的偏差,如string(const char*)
  • 规则 5:若类型提供非explicit初始化列表构造函数,也应提供非explicit默认构造函数,因为{}初始化使用默认构造函数。
  • 规则 6:除特定情况外,零参数构造函数应为非explicit
  • 规则 7:若类型为“标记类型”,其零参数构造函数应为explicit,因为非explicit构造函数可能无效,如in_place_index_t<N>
  • 规则 8:参数数量为 2 或更大的构造函数应为非explicit,如pair(piecewise_construct_t, tuple, tuple),但有正当理由的偏差,如explicit optional(in_place_t, Args&&...)
  • 规则 9:推导指南不应为explicit

当前标准库中的偏差:

  • <chrono>中有多个转换函数为explicit,其他地方也有一些转换函数为explicit的偏差。
  • 一些类型的构造函数存在偏差,如vector<bool>::reference::operator bool等。
  • 许多类型的单参数转换构造函数存在偏差。
  • string_viewspan的转换构造函数存在偏差。
  • scoped_allocator_adaptor的构造函数存在偏差。
  • 许多异常类型的构造函数存在偏差。
  • 一些标准库中的容器和实用类型的构造函数存在偏差,如各种流类、unordered_*系列等。
  • 一些代数类型的构造函数存在偏差。
  • locale::facet及其子类的构造函数存在偏差。
  • string的构造函数存在偏差。
  • span的构造函数存在偏差。
  • mdspan库的构造函数存在较多偏差。
  • out_ptr_t的构造函数存在偏差。
  • [thread]中的一些类型的构造函数存在偏差。
  • [rand]中的一些分布类的构造函数存在偏差。
  • 所有 Ranges 视图类型的构造函数存在偏差。

标准中的“标记类型”遵循规则 7 放弃规则 6 且不偏离规则。

阅读 39
0 条评论