在可能重叠的范围之间复制类

时间:2016-01-31 19:25:44

标签: c++

在C中,我们有函数memcpymemmove来有效地复制数据。如果源区域和目标区域重叠,前者会产生未定义的行为,但后者保证能够“按预期”处理,可能是通过注意重叠的方向和(如果需要)选择不同的算法。

当然,上述函数在C ++中可用(如std::memcpystd::memmove),但它们并不适用于非平凡的class es。相反,我们得到std::copystd::copy_backward。如果源和目标范围不重叠,则每个都有效;而且,每个都保证适用于重叠的一个“方向”。

如果我们想从一个区域复制到另一个区域,我们可以使用什么?我们在编译时不知道范围是否可能重叠或者可能出现重叠的方向?它没有似乎我们有一个选择。对于一般iterator s,可能很难确定范围是否重叠,所以我理解为什么在这种情况下没有提供解决方案,但当我们处理指针时呢?理想情况下,有一个像:

这样的功能
template<class T>
T * copy_either_direction(const T * inputBegin, const T * inputEnd, T * outputBegin) {
    if ("outputBegin ∈ [inputBegin, inputEnd)") {
        outputBegin += (inputEnd - inputBegin);
        std::copy_backward(inputBegin, inputEnd, outputBegin);
        return outputBegin;
    } else {
        return std::copy(inputBegin, inputEnd, outputBegin);
    }
}

(将T *替换为std::vector<T>::iterator的类似功能也会很好。如果inputBegin == outputBegin确保此功能有效,那就更好了,但那是a separate gripe of mine。 )

不幸的是,我没有看到在if语句中编写条件的合理方法,因为将指针与单独的内存块进行比较通常会产生未定义的行为。另一方面,实现显然有自己的方法来执行此操作,因为std::memmove本身就需要一个。因此,任何实现都可以提供这样的功能,从而满足程序员只是不能的需要。由于std::memmove被认为有用,为什么不copy_either_direction?有没有我错过的解决方案?

3 个答案:

答案 0 :(得分:0)

memmove之所以有效,是因为它指向连续字节的指针,因此要复制的两个块的范围是明确定义的。 copymove采用不一定指向连续范围的迭代器。例如,列表迭代器可以在内存中跳转;没有代码可以看到的范围,也没有重要的重叠概念。

答案 1 :(得分:0)

我最近了解到std::less专门针对指针提供了一个总订单,大概是为了允许人们在std::set s和相关class es中存储指针。假设在定义后者时必须同意标准排序,我认为以下内容将起作用:

#include <functional>

template<class T>
T * copy_either_direction(const T * inputBegin, const T * inputEnd, T * outputBegin) {
    if (std::less<const T *>()(inputBegin, outputBegin)) {
        outputBegin += (inputEnd - inputBegin);
        std::copy_backward(inputBegin, inputEnd, outputBegin);
        return outputBegin;
    } else {
        return std::copy(inputBegin, inputEnd, outputBegin);
    }
}

答案 2 :(得分:-1)

  

如果我们想要从一个地区复制到另一个地区,我们可以使用什么?如果范围可能重叠或者可能出现重叠的方向,我们在编译时不知道?

这不是一个逻辑上一致的概念。

复制操作后,您将拥有两个对象。每个对象都由一个单独的不同的内存区域定义。您不能拥有以这种方式重叠的对象(您可以拥有子对象,但对象类型不能是它自己的子对象)。因此,不可能将对象复制到其自身的一部分上。

移动一个对象位于其自身之上,在逻辑上不一致。为什么?因为移动在C ++中是虚构的;移动后,你仍然有两个功能完善的对象。移动操作仅仅是破坏性副本,窃取另一个对象拥有的资源。 仍然存在,它仍然是一个可行的对象。

由于物体不能与另一物体重叠,因此再次无法实现。

平凡的可复制类型可以解决这个问题,因为它们只是位块,没有析构函数或专门的复制操作。所以他们的一生并不像其他人那样严格。一个非平凡的可复制类型不能这样做,因为:

  

memmove的经验表明,在这种情况下可能会有一个解决方案(也可能是迭代器中的连续容器)。

对于在C ++中无法轻易复制的类型,这既不可能也不是通常所希望的。

琐碎可复制性的规则是该类型没有非平凡的复制/移动构造函数/赋值运算符,也没有非平凡的析构函数。一个简单的复制/移动构造函数/赋值只不过是一个memcpy,而一个简单的析构函数什么都不做。因此,这些规则有效地确保了类型只不过是一个比特块&#34;。一个&#34;比特块&#34;与另一个没有什么不同,所以通过memmove复制它是一个合法的结构。

如果一个类型有一个真正的析构函数,那么该类型正在维护某种需要实际维护的不变量。它可以释放指针或释放文件句柄或其他任何东西。鉴于此,复制位是没有意义的,因为现在你有两个对象引用相同的指针/文件句柄。这是一件坏事,因为班级通常会想要控制如何处理。

如果没有类本身参与复制操作,则无法解决此问题。不同的类在管理其内部方面具有不同的行为。实际上,这是具有复制构造函数和赋值运算符的对象的整个目的。因此,一个班级可以自己决定如何保持自己国家的理智。

它甚至不必是指针或文件句柄。也许每个类实例都有一个唯一的标识符;这样的值是在构造时生成的,并且永远不会被复制(新副本获得新值)。对于您违反此类限制memmove会使您的程序处于不确定状态,因为您将拥有期望此类标识符唯一的代码。

这就是为什么memmove为非平凡的可复制类型会产生未定义的行为。