提问人:Adam 提问时间:8/15/2008 最后编辑:sbiAdam 更新时间:10/12/2021 访问量:38026
如何重载 std::swap()
How to overload std::swap()
问:
std::swap()
在排序甚至赋值期间被许多 std 容器(例如 和 )使用。std::list
std::vector
但是 std 的实现非常通用,并且对于自定义类型来说效率相当低。swap()
因此,可以通过重载自定义类型特定的实现来获得效率。但是,您如何实现它,以便 std 容器使用它呢?std::swap()
答:
不允许(根据 C++ 标准)重载 std::swap,但明确允许将自己的类型的模板专用化添加到 std 命名空间。例如
namespace std
{
template<>
void swap(my_type& lhs, my_type& rhs)
{
// ... blah
}
}
然后,STD 容器(以及其他任何地方)中的用法将选择您的专业化,而不是一般专业化。
另请注意,提供 swap 的基类实现对于派生类型来说还不够好。例如,如果您有
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
虽然通常不应该向 std:: 命名空间添加内容是正确的,但明确允许为用户定义类型添加模板专用化。重载函数不是。这是一个微妙的区别:-)
17.4.3.1/1 C++ 程序添加声明或定义是未定义的 除非另有说明,否则 STD 命名空间或命名空间 std 的命名空间 指定。程序可以为任何 标准库模板到命名空间 std。这样的专业化 (完整或部分)标准库导致 undefined 行为,除非声明依赖于用户定义的名称 外部链接,除非模板专业化满足 原始模板的标准库要求。
std::swap 的专用化如下所示:
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
如果没有模板<>位,它将是未定义的重载,而不是允许的专用化。@Wilka建议的更改默认命名空间的方法可能适用于用户代码(由于 Koenig 查找更喜欢无命名空间版本),但不能保证,事实上也不应该这样做(STL 实现应该使用完全限定的 std::swap)。
comp.lang.c++.moderated 上有一个线程,对这个话题进行了长时间的讨论。不过,其中大部分是关于部分专业化(目前没有好的方法可以做到)。
评论
::swap
std::swap
vector
重载实现(又名专用化)的正确方法是将其编写在与要交换的内容相同的命名空间中,以便可以通过参数相关查找 (ADL) 找到它。一件特别容易做的事情是:std::swap
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
注意 Mozza314
这是对泛型调用效果的模拟,并让用户在命名空间 std 中提供他们的交换。由于这是一个实验,因此此模拟使用 .std::algorithm
std::swap
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
Dave和我都是委员会成员,并且已经在标准的这一领域工作了十年(并且并不总是彼此一致)。但这个问题已经解决了很长时间,我们都同意它是如何解决的。无视戴夫在这方面的专家意见/答案,后果自负。
这个问题是在 C++98 发布后曝光的。大约从2001年开始,Dave和我开始在这个领域工作。这是现代解决方案:
// 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");
}
}
工程!那么为什么不使用它呢?
考虑 your 是类模板的情况: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);
}
现在它不再起作用了。:-(
因此,您可以放入命名空间 std 并让它工作。但是,当您有模板时,您需要记住将 的命名空间放入 : .而且,由于如果您将命名空间放入命名空间,这两种情况都有效,因此更容易记住(并教其他人)以一种方式执行此操作。swap
swap
A
A<T>
swap
A
评论
template <>
exp::swap(A, A)
using std::swap
swap
swap
operator==
operator==
swap
template<class T> struct A
swap
std
评论