有几种写方法swap
,有些比其他更好。但是,随着时间的流逝,发现单个定义最有效。让我们考虑一下如何考虑编写swap
函数。
我们首先看到类似的容器std::vector<>
具有单参数成员函数swap
,例如:
struct vector
{
void swap(vector&) { /* swap members */ }
};
那么,我们的班级自然也应该吧?好吧,不是真的。标准库包含各种不必要的东西,而成员swap
就是其中之一。为什么?我们继续。
我们应该做的是确定什么是规范的,以及班级需要做什么才能使用它。交换的规范方法是std::swap
。这就是为什么成员函数没用的原因:它们通常不是我们应该交换的东西,并且与的行为无关std::swap
。
那么,要进行std::swap
工作,我们应该提供(并且std::vector<>
应该已经提供)的专业化std::swap
,对吗?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
在这种情况下,这当然可以工作,但是存在一个明显的问题:函数专业化不能是部分的。也就是说,我们不能仅使用特定的实例化来专门化模板类:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
此方法在某些时间有效,但并非所有时间都有效。肯定有更好的办法。
有!我们可以使用一个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
什么是friend
功能?这个区域周围有混乱。
在C ++标准化之前,friend
函数执行了一种称为“朋友名称注入”的操作,其中代码的行为就好像函数是在周围的名称空间中编写的一样。例如,这些是等效的预标准:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
但是,在发明ADL时就将其删除。friend
然后只能通过ADL找到该功能。如果您希望将其作为自由函数,则需要这样声明它(例如,参见this)。但是!出现问题了。
如果仅使用std::swap(x, y)
,将永远不会找到您的重载,因为您已经明确地说“向内看std
,别无其他”!这就是为什么有人建议编写两个函数的原因:一个是通过ADL找到的函数,另一个是处理明确std::
资格的函数。
但是就像我们看到的那样,这并非在所有情况下都有效,并且最终导致丑陋的混乱。取而代之的是,惯用的交换走了另一条路:不是使类成为要提供的类std::swap
,而是由交换者来确保它们不使用qualified swap
,如上。只要人们知道,这种方法就可以很好地工作。但是这里存在一个问题:需要使用不合格的调用是不直观的!
为了简化此过程,像Boost这样的某些库提供了function boost::swap
,该函数只对进行了无限制的调用swap
,并将其std::swap
作为关联的命名空间。这有助于使事情再次变得简洁,但仍然令人沮丧。
请注意,C ++ 11中的行为没有变化std::swap
,我和其他人错误地认为是这种情况。如果您对此有所了解,请在这里阅读。
简而言之:成员函数仅仅是噪声,专业化是丑陋且不完整的,而friend
函数却是完整且有效的。而当你交换,无论是使用boost::swap
或不合格的swap
有std::swap
关联的。
†非正式地,如果在函数调用期间要考虑名称,则将其关联。有关详细信息,请参阅第3.4.2节。在这种情况下,std::swap
通常不考虑;但是我们可以将其关联(将其添加到unqual认为的重载集合中swap
),以便找到它。