std::copy,std::copy_backward和重叠范围

std::copy, std::copy_backward and overlapping ranges

本文关键字:std copy 重叠 范围 backward      更新时间:2023-10-16

我的引用是std::copy和std::copy_backward。

模板<class InputIt,class OutputIt>OutputIt副本(InputItfirst,InputIt last,OutputIt d_first);

复制范围[第一个,最后一个]中从第一个和进行到最后一个-1。如果d_first在范围[first,last)。在这种情况下,可以使用std::copy_backward相反

模板<class BidirIt1,class BidirIt2>BidirIt2 copy_backward(BidirIt1第一,BidirIt1-最后,BidirIt2 d_last)

将[第一个,最后一个)定义的范围中的元素复制到以d_last结束的另一个范围。图元反向复制顺序(最后一个元素先复制),但它们的相对顺序是保存。

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

当复制重叠范围时,std::copy在复制时是合适的向左(目标范围的起点在源之外range),而std::copy_backward在复制到右侧(目标范围的末尾在源范围之外)。

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

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

然而,以下两项规定背后的技术原因是什么:

1) 在复制的情况下,如果d_first在范围[first,last]内,则会产生未定义的行为(意味着源范围复制到目标范围不成功,可能是系统故障)。

2) 在copy_backward的情况下,如果d_last在范围(first,last)内,则会产生未定义的行为(意味着将源范围复制到目标范围不成功,并且可能存在系统故障)。

我假设,一旦我理解了以上两种说法的含义,用copy_backward代替copy以避免上述未定义的行为场景的建议将变得显而易见。

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

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

附录

作为后续操作,我编写了以下测试代码来验证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

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

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

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

这里没有什么深层次的事情。只需使用一种简单的方法对样本数据执行一个算法:按顺序复制每个元素。

假设您有四元素数组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++标准定义的。由每个编译器来决定会发生什么。CCD_ 12的结果可以是任何结果。你可能会得到{0, 0, 0, 0}的天真结果。但是,您可能会得到{0, 0, 1, 2}的预期结果。其他结果也是可能的。你不能仅仅因为你足够幸运地得到了你想要的行为就断定没有未定义的行为<有时未定义的行为会给出正确的结果>(这就是追踪与未定义行为相关的错误如此困难的原因之一。)

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

正如C++中常见的那样,为了避免强制实现采取这一极端步骤,标准只是"告诉"您不要这样做,因为结果是未定义的。

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

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

但是,您认为此处未定义的行为会导致复制失败(或系统故障)的假设也是错误的。我的意思是,这很可能是的结果(JaMiT很好地证明了这是如何发生的),但你不能陷入从具有未定义行为的程序中期望任何特定结果的陷阱;这就是问题的关键。事实上,有些实现甚至可能会遇到让重叠范围副本"工作"的麻烦(尽管我不知道有任何这样的情况)。