有几种编写swap
函数的方法,其中一些比其他方法更好。然而,随着时间的推移,发现单个定义效果最佳。让我们考虑如何思考编写swap
函数。
首先我们可以看到像 std::vector<>
这样的容器有一个单参数成员函数 swap
,例如:
struct vector
{
void swap(vector&) { }
};
那么,我们的类也应该有一个成员函数 swap
吧?实际上并不需要。标准库有各种不必要的东西,其中一个就是成员函数 swap
。为什么呢?接下来我们来了解一下。
我们应该确定什么是规范的,以及我们的类需要做些什么才能与之配合。交换的规范方法是使用
std::swap
。这就是为什么成员函数没有用处的原因:它们通常不是交换事物的方式,并且对
std::swap
的行为没有影响。
那么,为了让
std::swap
起作用,我们应该提供一个
std::swap
的特化实现(
std::vector<>
应该提供),对吗?
namespace std
{
template <>
void swap(myclass&, myclass&)
{
}
}
那在这种情况下确实可以解决问题,但它存在一个明显的问题:函数特化不能是部分的。也就是说,我们不能使用此方法来专门化模板类,只能专门化特定的实例:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&)
{
}
}
这种方法有时候有效,但并非总是如此。一定有更好的方法。
有!我们可以使用一个friend
函数,并通过ADL找到它:
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
当我们想要交换某些东西时,我们会关联†std::swap
,然后进行未限定调用:
using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first
// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap
什么是友元函数
?这个领域存在一些混淆。
在C++标准化之前,友元函数
执行一种称为“友元名称注入”的操作,使得代码表现出好像该函数已在周围的命名空间中编写一样。例如,在标准化之前,以下代码是等效的:
struct foo
{
friend void bar()
{
}
};
struct foo
{
friend void bar();
};
void bar()
{
}
然而,当
ADL被发明后,这个问题就被解决了。现在
friend
函数只能通过ADL找到;如果你想要它作为一个自由函数,那么它需要被声明为这样(
例如这里)。但是,有一个问题。
如果你只使用
std::swap(x, y)
,你的重载将永远不会被找到,因为你已经明确地说“只在
std
中查找,其他地方都不用找”!这就是为什么有些人建议编写两个函数的原因:一个作为通过
ADL找到的函数,另一个用来处理显式的
std::
限定符。
但是,正如我们所看到的,这种方法并不能在所有情况下都奏效,最终导致代码变得混乱难懂。相反,惯用的交换方式采用了另一种方法:不是让类来提供
std::swap
,而是让使用者来确保他们不使用限定的
swap
,就像上面那样。只要人们知道这一点,这种方法通常都能很好地工作。但问题在于:需要使用未限定的调用方式很不直观!
为了让这更容易,一些库(如Boost)提供了函数
boost::swap
,它只是调用了未限定的
swap
函数,并将
std::swap
作为相关命名空间。这有助于再次使事情简洁,但仍然不太理想。
请注意,在C++11中,
std::swap
的行为没有改变,我和其他人错误地认为会发生变化。如果您受到此影响,请
在此处阅读。
简而言之:成员函数只是噪音,特化不美观且不完整,但友元函数完整且有效。当你交换时,要么使用
boost::swap
,要么使用未限定的
swap
与关联的
std::swap
。
非正式地,如果在函数调用期间考虑到它,则名称被视为关联。有关详细信息,请阅读§3.4.2。在这种情况下,std::swap
通常不会被考虑; 但是我们可以关联它(将其添加到未限定的 swap
考虑的重载集中),从而使其被找到。
friend
函数根本不是成员函数。 - aschepler