标准库是如何实现std::swap的?

82
STL中的swap函数是如何实现的?它是否像这样简单:
template<typename T> void swap(T& t1, T& t2) {
    T tmp(t1);
    t1=t2;
    t2=tmp;
}

在其他帖子中,他们谈论将此函数专门用于您自己的类。我为什么需要这样做?为什么不能使用std::swap函数?


28
这正是在C++03中实现std::swap的方式,而在C++11中,使用T tmp(std::move(t1)); t1 = std::move(t2); t2 = std::move(tmp);会比一些类型的拷贝更有效率。 - Jonathan Wakely
6
没有所谓的“标准库”。有许多种实现方式,选择其中之一或几个,进行查找。 - nwp
10
每个实现都实现了《C++标准库》(参见§17.1/1)。 - Mooing Duck
然而,没有所谓的“STL”。标准库不是STL。 - Mr Lister
6
@MrLister: 完全有“STL”。这是一个由SGI编写的C++库,与C++标准库非常相似。https://www.sgi.com/tech/stl/table_of_contents.html - Mooing Duck
2个回答

84

std::swap是如何实现的?

是的,问题中提出的实现方式是经典的C++03实现。

一个更现代化(C++11)的std::swap实现如下:

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

这是相对于经典的C++03实现,在资源管理方面的一种改进,因为它可以避免不必要的拷贝等。C++11中的 std::swap 需要类型 T 是可移动构造和可移动赋值的(MoveConstructible and MoveAssignable),从而允许实现和改进。

我为什么需要提供自定义实现?

通常建议为特定类型提供swap的自定义实现,当您的实现比标准版本更高效或更具体时。

这里有一个经典(C++11之前)的例子,当您的类管理大量昂贵的资源需要复制并删除时,您的自定义实现可以仅交换执行交换所需的句柄或指针。

随着 std::move 和可移动类型的出现 (并实现为此类) , 大部分最初的理由正在逐渐消失,但是如果自定义的交换函数比标准的更好,那就实现它。

通用代码一般都能够使用您的自定义 swap ,如果它适当地使用ADL机制


2
你能给一个例子,即使类实现了移动构造函数,仍然有意义专门化swap吗? - Maximilian Mordig
2
可能的一个原因是,如果设置已移动对象的状态不是完全琐碎的,并且在标准的swap实现中没有被优化掉,那么您可以通过不费心进行该设置来编写比三次移动更有效的交换。当然,这有很多“如果”,但假设一个已移动对象被定义为空,并且“空”对象包含许多零和可能的虚拟节点结构。这是几个写入,乘以三。因此通常可以忽略不计,但如果出现性能问题,则可以执行此操作。 - Steve Jessop
2
或者考虑使用具有短字符串优化的字符串。当然,您需要实现移动构造函数(以传输长字符串的动态缓冲区),但您还可以实现交换函数,以更高效地尝试交换短字符串缓冲区的内容,而不是将缓冲区内容复制三次到三个不同的内存区域中。 - Steve Jessop
2
@ratchetfreak:我说的是即使“尽可能少的资源消耗”比“零成本”更昂贵的情况。如果你的意思是移动状态应该是零成本建立,那么好吧,但是虽然在几乎所有情况下清除指针字段实际上是零成本,但它并不严格如此。 - Steve Jessop
5
如果临时破坏不变量比需要在每个移动后维护不变量进行三次完整移动更快,那么 swap 成员函数可以临时破坏不变量。如果存在有效的 X::swap(X&) 成员,则将 swap(X&, X&) 进行特化以调用它是有意义的。一些移动操作甚至可以基于 swap 成员实现,因此通用的 std::swap 函数会调用 swap 成员三次,而实际上只需要执行一次。 - Jonathan Wakely
显示剩余4条评论

21
STL中的交换函数是如何实现的?
哪一种实现?它是一个规范,不是一个具体的库。如果你想知道我的编译器标准库是如何实现的,要么告诉我们你使用的是哪个编译器,要么自己阅读代码。
它就是这么简单吗?
这本质上是C++11之前的朴素版本。
这个未特化的实现强制进行了复制:对于你的例子中的T = std::vector<SomethingExpensive>,代码转换为:
template<typename T> void swap(T& t1, T& t2) {
  T tmp(t1); // duplicate t1, making an expensive copy of each element
  t1=t2;     // discard the original contents of t1,
             // and replace them with an expensive duplicate of t2
  t2=tmp;    // discard the original contents of t2,
             // and replace them with an expensive duplicate of tmp
}            // implicitly destroy the expensive temporary copy of t1

为了交换两个向量,我们实际上创建了三个向量。这里涉及到了三个动态分配以及大量昂贵的对象复制,而且任何一个操作都可能抛出异常,可能会使参数处于不确定状态。

由于这显然是糟糕的,因此为昂贵的容器提供了重载,并鼓励您为自己的昂贵类型编写重载:例如,std::vector专业化可以访问向量的内部,可以在不进行所有复制的情况下交换两个向量:

template <typename T> void swap(vector<T> &v1, vector<T> &v2) { v1.swap(v2); }
template <typename T> void vector<T>::swap(vector<T>& other) {
  swap(this->size_, other.size_); // cheap integer swap of allocated count
  swap(this->used_, other.used_); // cheap integer swap of used count
  swap(this->data__, other.data_); // cheap pointer swap of data ptr
}

请注意,这不涉及任何昂贵的复制,没有动态分配内存,且保证不会抛出异常。
现在,为什么要进行这种特殊化呢?因为vector::swap可以访问vector的内部,并安全有效地移动它们,而无需复制。
那么,“为什么需要为自己的类进行这种特化呢?”在C++11之前,出于与std::vector相同的原因——使交换高效和异常安全。但是,如果您提供了移动构造和赋值,或者编译器可以为您生成合理的默认值,则实际上不需要这样做。
新的通用交换:
template <typename T> void swap(T& t1, T& t2) {
    T temp = std::move(t1);
    t1 = std::move(t2);
    t2 = std::move(temp);
}

可以使用移动构造/赋值来获得与上述自定义向量实现本质相同的行为,而无需编写自定义实现。


我使用Visual Studio 2005 c++(不支持c++11,但忽略此点)。我如何访问源代码?我找不到任何东西;它可能是专有的。 - Maximilian Mordig
1
头文件需要放在编译器可以使用的某个位置,但我不知道路径。 - Useless
2
@UnnamedOne 在 Visual Studio 中,您可以右键单击“swap”并选择“转到定义”。 - nwp
@UnnamedOne,有一个快捷键可以跳转到定义,否则请查看安装目录(在程序文件中)并查找名为“include”的文件夹。 - ratchet freak
@UnnamedOne 我认为2005的默认目录是 "C:\Program Files (x86)\Microsoft Visual Studio 8.0\VC\include"。 - Mooing Duck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接