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

Max*_*dig 58 c++ templates swap

如何在STL中实现交换功能?它是这么简单:

template<typename T> void swap(T& t1, T& t2) {
    T tmp(t1);
    t1=t2;
    t2=tmp;
}
Run Code Online (Sandbox Code Playgroud)

在其他帖子中,他们谈论为您自己的班级专门化这个功能.我为什么要这样做?为什么我不能使用这个std::swap功能?

Nia*_*all 57

如何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);
}
Run Code Online (Sandbox Code Playgroud)

这是对资源管理方面的经典C++ 03实现的改进,因为它可以防止不需要的副本等.它,C++ 11 std::swap,要求类型TMoveConstructibleMoveAssignable,从而允许实现和改进.

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

swap当您的实现比标准版本更高效或特定时,通常会建议针对特定类型的自定义实现.

一个典型的例子就是当你的类管理大量资源时,复制然后删除会很昂贵.相反,您的自定义实现可以简单地交换实现交换所需的句柄或指针.

随着std::move可移动类型的出现(并且实现了你的类型),大约在C++ 11及之后,这里的许多原始理由开始消失; 但是,如果自定义交换比标准交换更好,则实现它.

swap如果适当地使用ADL机制,通用代码通常可以使用您的自定义.

  • 如果这样做比在每次移动后必须维持不变量的三个完整移动更快,那么`swap`成员函数可以暂时中断不变量.如果有一个高效的`X :: swap(X&)`成员,那么专门调用`swap(X&,X&)`来调用它是有意义的.有些移动操作甚至可以用`swap`成员来实现,所以通用`std :: swap`函数会调用`swap`成员三次,当它只需要做一次. (4认同)
  • 您能否举一个例子,说明如果类实现了移动构造函数,那么专门化交换仍然有意义? (2认同)
  • 一个可能的原因是,如果设置移动对象的状态不是*完全*微不足道的,并且没有在标准的“交换”实现中优化掉,那么您可以编写比三个移动更有效的交换通过不打扰该设置。当然,这是很多“ifs”,但假设一个移动对象被定义为“空”,而一个“空”对象包含一大堆零,可能还有一个虚拟节点结构。写了几遍,写了三遍。所以通常可以忽略不计,但如果出现这种情况,您可能会为了性能而这样做。 (2认同)
  • ...或者考虑使用短字符串优化的字符串.你当然要实现移动构造函数(传输长字符串的动态缓冲区),但你可能*也*实现交换以便(尝试)交换短字符串缓冲区的内容比三个副本更有效缓冲区内容为三个不同的内存区域. (2认同)
  • @ratchetfreak:我说的是“最低资源可能”比“零成本”贵的情况。如果您表示从转移状态建立应该为零成本,那么可以,但是尽管在几乎所有情况下清除指针字段实际上都是零成本,但并非严格如此。 (2认同)

Use*_*ess 18

如何在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
Run Code Online (Sandbox Code Playgroud)

所以交换我们从根本上创造两个向量3.有三个动态分配和许多昂贵的对象被复制,并且任何这些操作都可能抛出,可能使参数处于不确定状态.

由于这显然很糟糕,为昂贵的容器提供了重载,并且鼓励您为自己的昂贵类型编写重载:例如.在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
}
Run Code Online (Sandbox Code Playgroud)

请注意,这不涉及任何昂贵的任何副本,没有动态(de)分配,并且保证不会抛出.

现在,这种专业化的原因是vector :: swap可以访问vector的内部结构,并且可以安全有效地移动它们而无需复制.

为什么我需要这样做[专门为你自己的班级]?

Pre-C++ 11,出于同样的原因std::vector- 使交换高效且异常安全.

从C++ 11开始,你真的没有 - 如果你提供移动构造和赋值,或者编译器可以为你生成合理的默认值.

新的通用交换:

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

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

  • @UnnamedOne在Visual Studio中,您可以右键单击`swap`并选择"Go To Definition". (2认同)