是否可以移动引用以避免大量的复制构造函数调用?

byt*_*e77 0 c++ constructor object-reference copy-constructor

比方说,我们想要反转一个数组,就像这个函数一样。

对于两个元素的每次交换,都会进行复制构造函数析构函数两个复制赋值。

template<class T>
void Reverse(T *arr, int size)
{
    T *leftItem = arr;
    T *rightItem = &arr[size - 1];

    for (int i = 0; i < size / 2; i++, leftItem++, rightItem--)
    {
        T swap = *leftItem;         // Copy constructor
        *leftItem = *rightItem;     // Copy assignment
        *rightItem = swap;          // Copy assignment
    }                               // Destructor
}
Run Code Online (Sandbox Code Playgroud)

这看起来很可怕。所以我想出了一个想法,但我不确定它是好还是坏,因为我实际上在这里安全地绕过了所有类型和约定。

但另一方面,它避免了复制操作,根据类型的不同,复制操作可能会很繁重。

template<class T>
void ReverseUsingMemcpy(T *arr, int size)
{
    char *swap = new char[sizeof(T)];
    T *leftItem = arr;
    T *rightItem = &arr[size - 1];

    for (int i = 0; i < count / 2; i++, leftItem++, rightItem--)
    {
        memcpy(swap, (char*)leftItem, sizeof(T));
        memcpy((char*)leftItem, (char*)rightItem, sizeof(T));
        memcpy((char*)rightItem, swap, sizeof(T));
    }

    delete[] swap;
}
Run Code Online (Sandbox Code Playgroud)

那么,如果 的类型<T>可能是任何类类型,我的方法是否合适?有什么好处,或者真正避免这种情况的原因是什么?是否有一种首选方法可以在不破坏任何内容的情况下移动结构的内容?

注意:我知道类型std::vector,但我还想更多地了解引用和类型安全。

Mil*_*nek 7

您提出的解决方案仅对于普通可复制类型(即那些没有用户定义的复制构造函数或复制赋值运算符的类型)是安全的。

即使在处理普通可复制类型时,也没有理由在这种情况下使用memcpy。与简单地使用赋值运算符相比,它的可读性较差,并且性能也没有提高。

考虑一个非常简单(无用)的示例,您提出的解决方案将失败:

struct SelfRef
{
    SelfRef* self_;

    SelfRef() : self_{this} {}
    SelfRef(const SelfRef&) : self_{this} {}
    SelfRef& operator=(const SelfRef&) { return *this; }  // no-op
};
Run Code Online (Sandbox Code Playgroud)

通过复制此类对象memcpy将直接复制self_字段,绕过复制构造函数或复制赋值运算符,并导致self_指向与 不同的位置this演示

一般来说,复制构造函数和复制赋值运算符的存在是为了在复制类时维护类的不变量。绕过具有赋值运算符的类可以并且很快会导致这些不变量被违反,并且您的类对其状态所做的假设变得不正确。


交换两个对象的正确方法是使用swap针对交换类型进行适当优化的适当函数。通常的方法是拉std::swap入本地命名空间,并让 ADL 选择一个更好的swap函数(如果可能):

template<class T>
void Reverse(T *arr, int size)
{
    T *leftItem = arr;
    T *rightItem = &arr[size - 1];

    for (int i = 0; i < size / 2; i++, leftItem++, rightItem--)
    {
        using std::swap;
        swap(*leftItem, *rightItem);
    }
}
Run Code Online (Sandbox Code Playgroud)

演示

这样,您可以swap在与该类型相同的命名空间中提供专门针对您的类型的函数,ADL 会找到它,而标准库类型、内置类型和没有优化函数的类型swap将回退到std::swap(这也可能是有针对不同类别类型的优化版本)。

您还可以使用std::iter_swap,它在使用迭代器时为您实现相同的语义:

template<class T>
void Reverse(T *arr, int size)
{
    T *leftItem = arr;
    T *rightItem = &arr[size - 1];

    for (int i = 0; i < size / 2; i++, leftItem++, rightItem--)
    {
        std::iter_swap(leftItem, rightItem);
    }
}
Run Code Online (Sandbox Code Playgroud)

演示


当然,在现实世界中,您应该只使用std::reverse而不是滚动自己的Reverse函数。