【问题标题】:Is it legal to convert a one past end of C++ array pointer into an interator?将 C++ 数组指针的过去一端转换为插入器是否合法?
【发布时间】:2016-11-24 17:52:51
【问题描述】:

我有这个代码:

#include <set>

int main() {
    int array[] = { 0 };
    std::set<int> stdset(&array[1], &array[1]);
}

获取最后一个数组元素之后的元素地址并将其转换为迭代器。基本上和std::vector::end()做的一样。

这样做是合法的:

 std::vector<int> vec;
 std::set<int> stdset(vec.end(), vec.end());

因为“最后一个”迭代器是一个非包含限制。

在第一个代码 sn-p 中对原始数组做同样的事情是否合法?

【问题讨论】:

  • 标准容器范围构造函数是模板,不涉及转换。
  • 旁白:&amp;array[0] + 1 是合法的。更简单的array + 1也是如此。
  • 这是个好问题。我根本不确定&amp;array[n] 是否合法(而不是array + n&amp;array[0] + n,它们是合法的)。我认为它独立于迭代器:问题仅在于表达式 &amp;array[n] 是否合法。 -> 这是问答:stackoverflow.com/questions/988158/…
  • 记录一下,您根本无法将指针转换为迭代器,因此title问题的答案显然是“否”。

标签: c++ arrays pointers iterator language-lawyer


【解决方案1】:

您可以在 C++ 中获取任何数组的末尾的地址。

您不能取消引用并使用结果,但您可以将其与指向同一数组的其他指针以及与同一数组的其他 one-past-the-end 的指针进行比较。

您在 OP 中所做的是已定义的行为,并且表示一个空范围。

【讨论】:

    【解决方案2】:

    不涉及转换。指针支持与随机访问迭代器相同的操作(一元 * 运算符、+++ 运算符等),因此可以用作迭代器。采用迭代器的标准库函数根据迭代器的类型进行模板化,因此它们将采用指针而不将其转换为任何内容。

    这以及指向数组末尾之后的指针是有效的(只要您不取消引用它)这一事实意味着您的代码是正确的。

    【讨论】:

      【解决方案3】:

      您没有转换任何东西:指针向量上的有效迭代器。

      获取一个指向刚刚超过数组末尾的元素的指针是合法的,但取消引用这样的指针是不合法的。

      ANSI C,第 5.7 节第 5 段:

      当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始数组元素的下标之差等于积分表达式。换句话说,如果表达式 P 指向数组对象的第 i 个元素,则表达式 (P)+N(等效于 N+(P))和 (P)-N(其中 N 的值为 n)指向分别到数组对象的第 i+n 个和第 i-n 个元素,前提是它们存在。此外,如果表达式 P 指向数组对象的最后一个元素,则表达式 (P)+1 指向数组对象的最后一个元素,如果表达式 Q 指向数组对象的最后一个元素,则表达式 (Q)-1 指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。

      我没有在网上的 C++ 标准报价中找到对此的引用,而且我没有 C++ 标准的副本,所以我无法证明它仍然是最新的 C++,但它可能是。

      TL;DR:您的代码是合法的。

      【讨论】:

        【解决方案4】:

        老实说,根据任何规格,我不知道您的问题的答案。尽管如此,我在您的方法中看到的问题是您正在将您的实现与您正在使用的库中的实现细节联系起来。

        如果您将您的实现与您使用的库所公开的接口联系起来,那么如果这些库的实现发生变化,您的消费代码就不太可能被破坏。在这种特殊情况下,这可能不是很相关,因为数组的内存布局在不久的将来不太可能发生变化,但如果发生了变化,运行时库开发人员也可能相应地更改迭代函数的实现,所以如果您使用公开的功能,您的代码应该继续按预期工作。但是,如果您的代码依赖于库的实现细节,则可能是您必须检查所有案例使用并相应地更改它们。

        编辑:

        对不起,我觉得我表达的不是很清楚;我不是在谈论您的代码,而是在谈论您的方法。封装的好处之一是它允许编写代码组件,这些组件在各自的任务中表现出色,然后通过组合多个代码组件提供的功能来应用程序。拥有多个抽象层次使我们能够设计上层而不必担心下层的微小细节。

        如果构成整个应用程序的不同组件保持相互独立的实现细节,则组件可以轻松升级而不会破坏兼容性,只要组件保持其最小接口并且它们的实现行为正确。如果不同的组件相互依赖于彼此的实现,那么升级就会变得更加困难,因为需要对所涉及组件的内部结构进行更改;在较低级别的组件中看似无害的修改(例如在两个较旧的组件之间插入新成员变量)可能会“巧妙地”依赖于组件的内部结构在完全不相关的代码段中产生完全无法预料的后果。

        在编程艺术中,您可以使用找到的任何资源将输入转换为输出,但这并不意味着所有可能性都具有相同的含义。如果您担心执行时间并且调用函数的开销是不可接受的,那么您可能会通过完全跳过面向对象的方法并按索引迭代数组来获得一些额外的周期。如果执行时间不是那么关键,以至于它允许调用接口上的公共方法,那么通过使用它,您将获得更小的升级噩梦。

        【讨论】:

        • 不知道你指的是什么库或者实现细节,但是数组的内存布局是标准中规定的,不能更改。
        • 感谢您的反馈,interjay。如果您觉得它有趣,我已经扩展了我的答案。祝你有美好的一天!
        猜你喜欢
        • 2020-07-31
        • 2012-01-27
        • 2017-01-19
        • 2015-04-28
        • 1970-01-01
        • 2020-11-22
        • 2011-04-03
        • 2012-01-18
        • 2022-01-03
        相关资源
        最近更新 更多