为什么有这么多std::swap的专业化

Why are there so many specializations of std::swap?

本文关键字:swap 专业化 std 为什么      更新时间:2023-10-16

在查看std::swap的文档时,我看到了很多专业化
看起来每个STL容器以及许多其他std设施都有一个专门的交换
我想有了模板的帮助,我们就不需要所有这些专业了吗?

例如,
如果我编写自己的pair,它可以正确地使用模板版本:

template<class T1,class T2> 
struct my_pair{
T1 t1;
T2 t2;
};
int main() {
my_pair<int,char> x{1,'a'};
my_pair<int,char> y{2,'b'};
std::swap(x,y);
} 

那么,对std::pair进行专门化可以获得什么呢?

template< class T1, class T2 >
void swap( pair<T1,T2>& lhs, pair<T1,T2>& rhs );

我还想知道我是应该为自定义类编写自己的专业化,
还是仅仅依赖于模板版本。

那么,从专门化std::pair中获得了什么呢?

性能。通用交换通常足够好(因为C++11),但很少是最优的(对于std::pair和大多数其他数据结构)。

我还想知道我是应该为自定义类编写自己的专业化,还是仅仅依赖模板版本。

我建议默认情况下依赖模板,但如果分析显示它是一个瓶颈,请知道可能还有改进的空间。过早的优化等等。。。

std::swap按照以下代码实现:

template<typename T> void swap(T& t1, T& t2) {
T temp = std::move(t1); 
t1 = std::move(t2);
t2 = std::move(temp);
}

(有关更多信息,请参阅"标准库如何实现std::swap?"。)

那么,专门化std::pair可以获得什么呢?

std::swap可以通过以下方式专门化(从libc++简化而来)

void swap(pair& p) noexcept(is_nothrow_swappable<first_type>{} &&
is_nothrow_swappable<second_type>{})
{
using std::swap;
swap(first,  p.first);
swap(second, p.second);
}

如您所见,使用ADL在对的元素上直接调用swap:这允许在firstsecond上使用swap的定制且可能更快的实现(这些实现可以利用元素内部结构的知识来获得更高的性能)

(有关更多信息,请参阅"using std::swap如何启用ADL?"。)

这可能是出于性能原因,因为pair包含的类型交换起来很便宜,但复制起来很昂贵,比如vector。由于它可以在firstsecond上调用交换,而不是用临时对象进行复制,因此它可以显著提高程序性能。

原因是性能,尤其是在c++11之前。

考虑类似于";矢量";类型Vector有三个字段:大小、容量和指向实际数据的指针。它的复制构造函数和复制赋值复制实际数据。C++11版本还有一个移动构造函数和移动赋值,可以窃取指针,将源对象中的指针设置为null。

专用的Vector交换实现可以简单地交换字段。

基于复制构造函数、复制赋值和析构函数的通用交换实现将导致数据复制和动态内存分配/释放。

基于移动构造函数、移动赋值和析构函数的通用交换实现将避免任何数据复制或内存分配,但它将留下一些冗余的无效和无效检查,优化器可能无法优化这些检查。

这意味着,在c++11之后,根据优化器的性能,通用交换可能比特定的交换稍差,但可能仍然相当便宜。另一方面,在c++11之前,通用的交换实现比特定的交换实现糟糕得多。


那么为什么要对";配对"?对于一对int和char,没有必要。它们都是普通的旧数据类型,所以一般交换就可以了。

但是如果我有一对向量和字符串呢?我想对这些类型使用专业的交换操作,所以我需要对pair类型进行交换操作,通过交换其组件元素来处理它。

交换两对向量的最有效方法与交换两个向量的最高效方法不同。这两种类型有不同的实现、不同的成员变量和不同的成员函数。

以这种方式"交换"两个对象不仅仅是通用的方法。

我的意思是,当然,对于可复制类型,你可以这样做:

T tmp = a;
a = b;
b = tmp;

但这太可怕了。

对于可移动类型,您可以添加一些std::move并防止复制,但仍然需要在下一层使用"交换"语义,以便真正具有有用的移动语义。在某种程度上,你需要专业化。

有一条规则(我认为它来自Herb Sutter的Exceptional C++或Scott Meyer的Effective C++系列),如果您的类型可以提供一个不抛出的交换实现,或者比通用std::swap函数更快,那么它应该作为成员函数void swap(T &other)来提供。

理论上,通用的std::swap()函数可以使用模板魔术来检测成员交换的存在,并调用它,而不是进行

T tmp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(tmp);

但似乎还没有人想过这一点,所以人们倾向于添加免费swap的过载,以便调用(可能更快的)成员交换。