std::swap()
被许多标准库容器(例如std::list
和std::vector
)在排序和赋值过程中使用。
然而,该函数的标准实现非常通用,对于自定义类型而言效率较低。
因此,可以通过为自定义类型重载std::swap()
函数来获得更高的效率。但是如何实现以便被标准库容器所使用呢?
std::swap()
被许多标准库容器(例如std::list
和std::vector
)在排序和赋值过程中使用。
然而,该函数的标准实现非常通用,对于自定义类型而言效率较低。
因此,可以通过为自定义类型重载std::swap()
函数来获得更高的效率。但是如何实现以便被标准库容器所使用呢?
重载 std::swap
的正确方式(也称为特化它)是在与需要交换的内容相同的命名空间中编写它,以便可以通过参数相关查找(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);
// ...
}
};
std::sort
在C++03中不符合规范,但在C++11中符合规范。此外,为什么要基于客户端可能使用非惯用代码来否定一个答案? - JoeG注意Mozza314
这是一个模拟泛型std::algorithm
调用std::swap
并让用户在namespace 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
swap
放入命名空间std
(exp
)也无法阻止它发生。// 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中,并使其正常工作。但是,对于存在模板的情况A<T>
,您需要记住将swap
放入A
的命名空间中。由于如果将swap
放入A
的命名空间中,两种情况都可以工作,因此仅以这种方式执行更容易记住(并教给其他人)。template <>
后,gcc 的输出是 exp::swap(A, A)
。那么,为什么不选择特化呢? - voltrevousing std::swap
。是的,swap
几乎是一个关键字,但并不完全是。因此,在确实需要时才将其导出到所有命名空间中最好。swap
就像 operator==
一样。最大的区别是几乎没有人会考虑使用限定命名空间语法调用 operator==
(它只是太丑陋了)。 - Howard Hinnantswap
函数。这增加了一个我以前没有考虑过的新问题:对于template<class T> struct A
情况,将您的swap
函数放入命名空间std
可能会使您的代码不可移植。请在wandbox上尝试您的示例,以查看gcc和clang如何处理它。 - Howard Hinnant根据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::swap
命名空间之外,重载std::swap
(或其他任何东西)是否被禁止? - Kosnamespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
没有模板<>的话,它将是一种未定义的过载,而不是允许的特化。@Wilka建议更改默认命名空间的方法可能适用于用户代码(由于Koenig查找更喜欢无命名空间版本),但这并不保证,并且实际上并不应该(STL实现应该使用完全限定的std::swap)。
有一个comp.lang.c++.moderated上的主题线程,其中有一个关于该主题的长讨论。大部分讨论都是关于部分特化的,虽然目前还没有很好的方法可以做到。
::swap
重载比vector
的std::swap
重载更为专业,因此它捕获了调用,后者的任何专业化都不相关。我不确定这是一个实际问题(但我也不声称这是一个好主意!)。 - Davis Herringswap
重载。这是一个实际问题,因为程序员认为他正在为特定类型定制swap
的行为,但他的定制可能会被不够具体的重载所替代。 - Dave Abrahams