【问题标题】:Why does std::array not have an operator T*?为什么 std::array 没有运算符 T*?
【发布时间】:2019-10-13 10:41:04
【问题描述】:

对于 C 数组,简单地命名一个数组与近 50 年来写 &foo[0] 的效果相同。

在我正在处理的项目中从 C 样式数组转换为 std::array 时,出现的绝大多数“错误”是由于 C 数组的上述属性造成的。

在所有情况下,解决方案都很简单,只需附加 .data()。但是我突然想到,精心设计的operator T* 应该可以直接解决这个问题。

是否有任何技术原因无法创建此运算符?

【问题讨论】:

  • 我个人认为调用.data() 比使用内置数组更安全。
  • 这个操作符不存在有什么好的理由吗? -- 很好 -- 另一个操作符在运行时被调用,而程序员可能不知道它。如果您注意到,大多数 C++ 标准库都远离强制转换运算符(也许所有标准库都这样做)。意识到std::string 和一般std::basic_string<> 也没有operator char*(),而是提供c_str() 函数。我的经验是,一个 C++ 程序员在代码库中多次使用强制转换运算符时并不知道自己调用了哪些函数。
  • @sepp2k 是的,我要找的是运营商 T*。我查看了几年前的一些代码,这些代码用于其他目的。在此期间,我的记忆消失了,我们在允许直接访问的上下文中使用operator T。至少在大多数情况下,它确实在某种程度上依赖于编译器在运行时推断目标类型的能力。在 99% 的情况下它工作正常,而对于 1% 的情况,我们有一个 .get() 方法来覆盖。我已经适当地编辑了这个问题。
  • 不隐式衰减到指针是使用std::array 而不是内置数组的最显着优势之一。
  • @L.F.隐式转换不是“衰减”。

标签: c++ c++11 language-lawyer implicit-conversion stdarray


【解决方案1】:

C 风格的数组是有大小的结构。您可以通过各种方式获取数组的编译时大小。但是,当数组衰减为指针时,您会丢失编译时的大小信息。即使参数是“数组”类型,它实际上仍然只是一个没有大小信息的指针。如果数组作为函数参数 (template<size_t S> void func(T(&param)[S])) 传递,您可以使用模板编程来保留大小,但仅此而已。

许多 C++ 程序员认为这种隐式衰减数组到指针是 C 样式数组中的一个设计缺陷。事实上,说有损转换可能不应该是隐式的,这几乎是不合理的。鉴于std::array 试图尽可能多地修复 C 样式数组的缺陷,让它隐式衰减为指针会适得其反。

相比之下,C++20's std::span type 提供从 std::array(以及 C 样式数组等)的隐式转换。原因是这样的转换保留了信息:指针和大小。实际上,转换甚至可以保留该大小的编译时性质。


是否有任何技术原因无法创建此运算符?

不能不”?没有。

【讨论】:

    【解决方案2】:

    data() 被引入以在多个 STL 容器中具有相似的接口。例如,std::string 和 std::vector 也提供了 data() ,它给出了实际数据缓冲区的地址。所以 std::array 接口被设计为匹配它。您建议的 Operator() 是完全不同的方式,并且似乎不是绝对合适的。正如上述评论者还提到的,更合适的是运算符 T*(),但这仍然不是 STL 设计人员更喜欢做的事情——他们引入了 data()。为什么?也许是因为它更具可读性。

    【讨论】:

    • "Operator()" 你的意思是operator()()吗?
    【解决方案3】:

    这个运算符不存在有什么好的理由吗?

    如果您的意思是为什么没有 operator T*() 自动提供演员表,那么(多个)潜在原因之一就是——自动演员表的想法。

    也许使用std::array 的代码可以很方便地使用operator T*() 正确编译,然后你就可以使用一个没有错误构建的程序。然而,这有一些含义:

    1) 对operator T*() 的调用是程序员可能不希望的潜在运行时成本,但只是顺其自然。在 C++ 中,我们的目标是只为想要的东西付费,而强制转换运算符只是强加于自己的想法违背了这个想法。

    2) operator T*() 和一般的转换操作符可能隐藏错误或 由于程序员不知道的用法,运行代码的瓶颈。

    根据我的经验,询问一个代码库中充斥着强制转换运算符的程序员实际上调用了哪些函数,而且很多时候,他们不确定他们的代码所采用的路径,或者他们只是完全错误(直到他们参加调试会议并看到所有被调用的看似无辜的铸造操作员所做的曲折时才意识到这一点。

    因此,程序员通过调用data() 之类的函数而不是使用他们可能不知道的operator T*() 来显式地“转换”数据被认为要安全得多。

    【讨论】:

    • Casting 从来都不是自动的:它需要编写一个演员表。这里的问题是自动转换。 +1。
    • 至于性能,以现代优化器的行为方式,这在很大程度上不是问题。 godbolt.org/z/gbRHuL 很好地展示了这一点,对于 GCC 和 MSVC,在最大优化时,转换是免费的。
    • @dgnuff -- 除非您可以绝对保证在任何情况下使用这样的运算符都不会产生任何运行时成本,否则它是行不通的。这就是我说潜在瓶颈的原因。实际上,在所有情况下都必须是 constexpr,而且不能保证。
    • @PaulMcKenzie 哪个 C++ 构造(不是 100% 编译时间)没有运行时成本保证?
    【解决方案4】:

    致电data() 是您应该避免的事情。拥有这种能力(如在我们的例子中迁移代码或优化代码处理胆量)可能很有用,但它违反了std::array 提供的安全网。

     std::array<int, 2> a{1,2};
     auto* ptr = a.data();
     std::cout << ptr[2]; // boom
    

    消除安全网的事情应该是明确的。

    【讨论】:

    • std::array 的 operator[] 没有任何边界检查。因此a.data()[2]a[2] 一样不安全。 (**尽管任何体面的编译器很可能会捕捉到这一点)
    • 我更多地归因于兼容性而不是好的设计。对于性能关键代码,如果 operator[] 已更改并添加 at_unsafe(),我更愿意。除此之外:还有第二个方面:std::array 在其声明中携带类型,使其易于使用(在代码审查和查找中),data() 返回的指针丢失了。
    • 你描述的行为在std::array::at中实现。如果您想要边界检查,请首选at 而不是operator[]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    相关资源
    最近更新 更多