2

我的参考是std::copystd::copy_backward

template< class InputIt, class OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt d_first );

复制范围 [first, last) 中的所有元素,从 first 开始,一直到 last - 1。如果 d_first 在 [first, last) 范围内,则行为未定义。在这种情况下,可以使用 std::copy_backward 代替。

模板< class BidirIt1, class BidirIt2 > BidirIt2 copy_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last )

将由 [first, last) 定义的范围中的元素复制到以 d_last 结尾的另一个范围。以相反的顺序复制元素(首先复制最后一个元素),但保留它们的相对顺序。

如果 d_last 在 (first, last] 之内,则行为未定义。在这种情况下,必须使用 std::copy 而不是 std::copy_backward。

复制重叠范围时,std::copy 适用于向左复制(目标范围的开头在源范围之外),而 std::copy_backward 适用于向右复制(目标范围的结尾在源范围之外)范围)。

根据以上描述,我得出以下推论:

copy 和 copy_backward 最终都将相同的源范围 [first, last) 复制到目标范围,尽管在前者的情况下,复制从第一个到最后一个 - 1,而在后者的情况下,复制从最后一个发生-1 到第一。在这两种情况下,源范围中元素的相对顺序都会保留在结果目标范围中。

但是,以下两个规定背后的技术原因是什么:

1) 在复制的情况下,如果 d_first 在 [first, last) 范围内,则会导致未定义的行为(暗示将源范围复制到目标范围的不成功以及可能的系统故障)。

2) 在 copy_backward 的情况下,如果 d_last 在范围内(第一个,最后一个),则会导致未定义的行为(暗示将源范围复制到目标范围的不成功以及可能的系统故障)。

我假设一旦我理解了上述两个语句的含义,将 copy 替换为 copy_backward 以避免上述未定义行为场景的建议对我来说将变得显而易见。

同样,我还假设在向左复制时提到复制的适当性(我不清楚这个概念),以及在向右复制时的 copy_backward(我也不清楚这个概念),将开始一旦我理解了 copy 和 copy_backward 之间的上述区别,就有意义了。

一如既往地期待您的有益想法。

附录

作为后续,我编写了以下测试代码来验证 copy 和 copy_backward 的行为,以实现相同的操作。

#include <array>
#include <algorithm>
#include <cstddef>
#include <iostream>

using std::array;
using std::copy;
using std::copy_backward;
using std::size_t;
using std::cout;
using std::endl;

int main (void)
{
    const size_t sz = 4;

    array<int,sz>a1 = {0,1,2,3};
    array<int,sz>a2 = {0,1,2,3};

    cout << "Array1 before copy" << endl;
    cout << "==================" << endl;

    for(auto&& i : a1) //the type of i is int&
    {
        cout << i << endl;
    }

    copy(a1.begin(),a1.begin()+3,a1.begin()+1);

    cout << "Array1 after copy" << endl;
    cout << "=================" << endl;

    for(auto&& i : a1) //the type of i is int&
    {
        cout << i << endl;
    }

    cout << "Array2 before copy backward" << endl;
    cout << "===========================" << endl;

    for(auto&& i : a2) //the type of i is int&
    {
        cout << i << endl;
    }

    copy_backward(a2.begin(),a2.begin()+3,a2.begin()+1);

    cout << "Array2 after copy backward" << endl;
    cout << "==========================" << endl;

    for(auto&& i : a2) //the type of i is int&
    {
        cout << i << endl;
    }


    return (0);
}

以下是程序输出:

Array1 before copy
==================
0
1
2
3
Array1 after copy
=================
0
0
1
2
Array2 before copy backward
===========================
0
1
2
3
Array2 after copy backward
==========================
2
1
2
3

显然, copy 会产生预期的结果,而 copy_backward 不会,即使 d_first 在 [first, last) 范围内。此外, d_last 也在范围内(第一个,最后一个),根据文档,这应该会在 copy_backward 的情况下导致未定义的行为。

所以实际上,在copy_backward的情况下,程序输出与文档一致,而在copy的情况下则不是。

再次值得注意的是,根据文档,在这两种情况下, d_first 和 d_last 确实满足分别导致 copy 和 copy_backward 未定义行为的条件。但是,仅在 copy_backward 的情况下才会观察到未定义的行为。

4

2 回答 2

2

这里没有什么深入的。只需使用简单的方法对样本数据进行算法运行:按顺序复制每个元素。

假设您有一个四元素数组int a[4] = {0, 1, 2, 3},并且您想将前三个元素复制到后三个元素。理想情况下,您最终会得到{0, 0, 1, 2}. 这(不)如何使用std::copy(a, a+3, a+1)

第 1 步:复制第一个元素a[1] = a[0];数组现在是{0, 0, 2, 3}.

第 2 步:复制第二个元素a[2] = a[1];数组现在是{0, 0, 0, 3}.

第 3 步:复制第三个元素a[3] = a[2];数组现在是{0, 0, 0, 0}.

结果是错误的,因为您在读取这些值之前覆盖了一些源数据 (a[1]和)。a[2]反向复制会起作用,因为以相反的顺序,您将在覆盖之前读取值。

由于一种合理方法的结果是错误的,因此标准将行为声明为“未定义”。希望采用幼稚方法的编译器可能会,并且他们不必考虑这种情况。在这种情况下犯错是可以的。采用不同方法的编译器可能会产生不同的结果,甚至可能是“正确”的结果。那也没关系。任何对编译器来说最简单的东西都符合标准。


根据问题的附录:请注意这是未定义的行为。这并不意味着行为被定义为与程序员的意图相反。相反,这意味着行为不是由 C++ 标准定义的。由每个编译器决定发生什么。的结果std::copy(a, a+3, a+1)可能是任何东西。你可能会得到{0, 0, 0, 0}. 但是,您可能会得到预期的结果{0, 0, 1, 2}。其他结果也是可能的。你不能仅仅因为你很幸运地得到了你想要的行为就断定没有未定义的行为。有时未定义的行为会给出正确的结果。(这是追踪与未定义行为相关的错误如此困难的原因之一。)

于 2019-10-06T01:22:39.003 回答
1

原因是,一般来说,将一个范围的一部分复制到同一范围的另一部分,可能需要额外的(如果只是临时的)存储,以便在从左到右或从右到左按顺序复制时处理重叠。第二个例子。

与 C++ 一样,为了避免强制实现采取这种极端步骤,标准只是告诉你不要这样做,说结果是未定义的。

在这种情况下,这迫使您通过自己复制到新的记忆中来明确。

它这样做的同时甚至不需要编译器付出任何努力来警告或告诉您这一点,这在标准方面也被视为“太专横”。

但是您认为此处未定义的行为会导致复制失败(或系统故障)的假设也是错误的。我的意思是,这很可能结果(JaMiT 很好地展示了这种情况是如何发生的),但你不能落入期望来自具有未定义行为的程序的任何特定结果的陷阱;这就是重点。实际上,某些实现甚至可能会遇到使重叠范围副本“工作”的麻烦(尽管我不知道有任何这样做)。

于 2019-10-06T02:30:23.173 回答