如何重载 std::swap()
-
08-06-2019 - |
题
std::swap()
被许多 std 容器使用(例如 std::list
和 std::vector
)在排序甚至分配期间。
但是 std 的实现 swap()
对于自定义类型来说非常通用并且效率相当低。
因此可以通过重载来提高效率 std::swap()
具有自定义类型的特定实现。但是如何实现它以便 std 容器使用它呢?
解决方案
重载交换的正确方法是将其写入与要交换的内容相同的命名空间中,以便可以通过以下方式找到它 参数相关查找 (ADL). 。一件特别容易做的事情是:
class X
{
// ...
friend void swap(X& a, X& b)
{
using std::swap; // bring in swap for built-in types
swap(a.base1, b.base1);
swap(a.base2, b.base2);
// ...
swap(a.member1, b.member1);
swap(a.member2, b.member2);
// ...
}
};
其他提示
关注Mozza314
这是通用效果的模拟 std::algorithm
呼叫 std::swap
, ,并让用户在命名空间 std 中提供交换。由于这是一个实验,因此该模拟使用 namespace exp
代替 namespace std
.
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
exp::swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
namespace exp
{
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
对我来说这打印出来:
generic exp::swap
如果您的编译器打印出不同的内容,那么它就没有正确实现模板的“两阶段查找”。
如果您的编译器符合(符合 C++98/03/11 中的任何一个),那么它将给出与我显示的相同的输出。在这种情况下,你担心会发生的事情确实会发生。并把你的 swap
进入命名空间 std
(exp
)并没有阻止它的发生。
戴夫和我都是委员会成员,十年来一直致力于该标准领域(但彼此的意见并不总是一致)。但这个问题已经解决了很长时间,我们双方都同意如何解决。忽视戴夫在该领域的专家意见/答案,后果自负。
这个问题是在 C++98 发布后才暴露出来的。大约从 2001 年开始,戴夫和我开始 在这个领域工作. 。这是现代的解决方案:
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
void swap(A&, A&)
{
printf("swap(A, A)\n");
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
输出是:
swap(A, A)
更新
观察结果表明:
namespace exp
{
template <>
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
作品!那么为什么不使用它呢?
考虑一下您的情况 A
是一个类模板:
// simulate user code which includes <algorithm>
template <class T>
struct A
{
};
namespace exp
{
template <class T>
void swap(A<T>&, A<T>&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A<int> a[2];
exp::algorithm(a, a+2);
}
现在又不行了。:-(
所以你可以把 swap
在命名空间 std 中并让它工作。但你需要记住把 swap
在 A
当您有模板时的情况下的命名空间: A<T>
. 。因为如果你把这两种情况都适用 swap
在 A
的命名空间,只是更容易记住(并教其他人)以一种方式进行操作。
(C++ 标准)不允许您重载 std::swap,但是特别允许您将自己类型的模板专业化添加到 std 命名空间。例如。
namespace std
{
template<>
void swap(my_type& lhs, my_type& rhs)
{
// ... blah
}
}
那么 std 容器(以及其他任何地方)中的用法将选择您的专业而不是通用的。
另请注意,提供交换的基类实现对于派生类型来说还不够好。例如。如果你有
class Base
{
// ... stuff ...
}
class Derived : public Base
{
// ... stuff ...
}
namespace std
{
template<>
void swap(Base& lha, Base& rhs)
{
// ...
}
}
这适用于基类,但如果您尝试交换两个派生对象,它将使用 std 的通用版本,因为模板化交换是完全匹配的(并且它避免了仅交换派生对象的“基本”部分的问题)。
笔记:我已更新此内容以删除上一个答案中的错误部分。噢!(感谢 puetzk 和 j_random_hacker 指出)
虽然通常不应该向 std:: 添加内容是正确的命名空间中,特别允许为用户定义类型添加模板专业化。函数重载则不然。这是一个微妙的区别:-)
17.4.3.1/1,除非另有说明,否则C ++程序不确定将声明或定义添加到名称空间std或名称空间的定义。程序可能会将任何标准库模板的模板专业添加到命名空间std。标准库的这种专业化(完整或部分)会导致不确定的行为,除非声明取决于用户定义的外部链接名称,否则模板专业化符合原始模板的标准库要求。
std::swap 的特化如下所示:
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
如果没有 template<> 位,它将是一个未定义的重载,而不是允许的特化。@Wilka 建议的更改默认名称空间的方法可能适用于用户代码(由于 Koenig 查找更喜欢无名称空间版本),但它不能保证,而且实际上并不真正应该(STL 实现应该使用完全- 合格的 std::swap)。
有一个 comp.lang.c++.moderated 上的线程 与一个 长的 主题的讨论。不过,其中大部分都是关于部分专业化(目前没有好的方法可以做到)。