如何重载std :: swap()
std::swap()
被许多std容器(如std::list
和std::vector
)在sorting和分配时使用。
但是,对于自定义types, swap()
的标准实现是非常普遍的,而且效率很低。
因此,通过使用自定义types特定的实现来重载std::swap()
可以获得效率。 但是,你怎么能实现它,所以它会被使用的性病容器?
重载交换的正确方法是将其写入与交换相同的名称空间,以便通过依赖于参数的查找(ADL)find它。 一个特别容易的事情是:
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年戴夫开始,我开始在这个领域工作 。 这是现代解决scheme:
// 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
放在命名空间标准,并使其工作。 但是当你有一个模板的时候,你需要记得把A
swap
A
的名字空间: A<T>
。 而且,既然这两种情况都会起作用,如果你把swap
放在A
的名字空间中,记住(并教导其他人)只用一种方法就可以了。
您不允许(通过C ++标准)重载std :: swap,但是您特别允许您将自己的types的模板特化添加到std名称空间。 例如
namespace std { template<> void swap(my_type& lhs, my_type& rhs) { // ... blah } }
那么在std容器(和其他任何地方)的用法将会select你的专业,而不是一般的。
还要注意,提供swap的基类实现对于派生types来说不够好。 如果你有
class Base { // ... stuff ... } class Derived : public Base { // ... stuff ... } namespace std { template<> void swap(Base& lha, Base& rhs) { // ... } }
这将适用于Base类,但是如果您尝试交换两个Derived对象,它将使用std的通用版本,因为模板交换是完全匹配的(并且避免了只交换派生对象的“基本”部分)。
注:我已经更新了这个从我最后的答案中删除错误的位。 D'哦! (感谢puetzk和j_random_hacker指出)
虽然通常不应该在std :: namespace中添加内容是正确的,但是为用户定义types添加模板专门化是特别允许的。 重载函数不是。 这是一个微妙的差别:-)
17.4.3.1/1除非另外指定,否则C ++程序未定义将声明或定义添加到名称空间标准或名称空间标准名称空间的命名空间。 程序可以将任何标准库模板的模板特化添加到名称空间标准。 除非声明依赖于用户定义的外部链接名称,并且除非模板专业化符合原始模板的标准库要求,否则标准库的这种专业化(完整或部分)会导致未定义的行为。
std :: swap的专门化看起来像这样:
namespace std { template<> void swap(myspace::mytype& a, myspace::mytype& b) { ... } }
如果没有模板<>位,它将是一个重载,这是未定义的,而不是专门化,这是允许的。 @ Wilkabuild议的改变默认名称空间的方法可能与用户代码一起工作(由于Koenig查找更喜欢无名称版本),但不能保证,实际上也不是真的(STL实现应该使用完全的-qualified std :: swap)。
Comp.lang.c ++上有一个线程,主题讨论很长 。 但大部分是关于部分专业化的(目前还没有好的办法)。