比方说,我们想要反转数组,就像这个函数一样。
对于两个元素的每次交换,都会进行复制构造函数、析构函数和两个复制赋值。
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
}
这看起来很可怕。所以我想出了一个想法,但我不确定它是好还是坏,因为我实际上在这里安全地绕过了所有类型和约定。
但另一方面,它避免了复制操作,根据类型,复制操作可能很繁重。
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;
}
那么,如果
<T>
的类型可能是任何类类型,我的方法是否合适?有什么好处,或者真正避免这种情况的原因是什么?是否有一种首选方法可以在不破坏任何内容的情况下移动结构的内容?
注意:我知道
std::vector
类型,但我还想更多地了解引用和类型安全。
您提出的解决方案仅对于普通可复制类型(即那些没有用户定义的复制构造函数的类型)来说是安全的,而且它的可读性较差,并且比简单地对此类类型使用复制赋值性能更高。
考虑一个非常简单(无用)的示例,您提出的解决方案将失败:
struct SelfRef
{
SelfRef* self_;
SelfRef() : self_{this} {}
SelfRef(const SelfRef&) : self_{this} {}
SelfRef& operator=(const SelfRef&) { return *this; } // no-op
};
通过
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);
}
}
这样,您可以在与该类型相同的命名空间中提供专门针对您的类型的
swap
函数,ADL 会找到它,而标准库类型、内置类型和没有优化 swap
函数的类型将被 ADL 找到。回退到 std::swap
(它也可能有针对不同类别类型的优化版本)。
当然,在现实世界中你应该只使用
std::reverse
而不是滚动你自己的 Reverse
函数。