swap函数存在于STL中,其典型实现如下:
namespace std
{
template <typename T>
void swap(T &a, T &b)
{
T Temp(a);
a = b;
b = Temp;
}
}
这个函数执行了对象a复制到temp,b复制到a,Temp复制到b,但是对于某些情况,多余的复制是没有必要的:
考虑下面的例子:
class WidgetImpl
{
public:
...
private:
int a, b, c;
std::vector<double> v;
};
class Widget
{
Widget(const Widget &rhs);
Widget &operator =(const Widget &rhs);
private:
WdigetImpl *pImpl;
};
一旦要交换两个Widget对象的值,我们仅仅需要的就是置换其pImpl指针,但缺省的算法不止复制三个Widgets,还复制三个WidgetImpl对象,非常低效。
我们需要告诉缺省的swap函数,只需要交换两个指针就好,为了实现这个思路,考虑如下做法:
namespace std
{
template <>
void swap<Widget>(Widget &a, Widget &b)
{
swap(a.pImpl, b.pImpl);
}
}
这个做法叫做函数的特化https://mp.csdn.net/postedit/83651232。
但是上述做法并不能通过编译,因为pImpl是类的私有成员,我们无法在类外调用。
既然如此,那么我们就让特化的swap函数成为Widget类的类成员:
class Widget
{
public:
...
Widget(const Widget &rhs);
Widget &operator =(const Widget &rhs);
public:
void swap(Widget &Other)
{
using std::swap;
swap(pImpl, Other.pImpl);
}
private:
WdigetImpl *pImpl;
};
namespace std
{
template <>
void swap<Widget>(Widget &a, Widget &b)
{
a.swap(b);
}
}
这样,就可以通过编译。
上述做法是基于类都是class而不是template class,下面考虑一下如果类都是template class会发生什么:
template <typename T>
class WidgetImpl{...};
template <typename T>
class Widget{...};
namespace std
{
template <typename T>
void swap<Widget<T>>(Widget<T> &a, Widget<T> &b)
{
a.swap(b);
}
}
这种情况是不能通过编译的,因为函数是不能够被偏特化的,只有类才能偏特化。
一般情况下,偏特化一个函数的代替做法为重载函数:
namespace std
{
template <typename T>
void swap(Widget<T> &a, Widget<T> &b)
{
a.swap(b);
}
}
但是这也是不合法的,因为命名空间std内是禁止新的东西的,你可以全特化,但不可以重载。
添加一个新的命名空间可以解决这种局面:
namespace WidgetStuff
{
template <typename T>
class WidgetImpl{...};
template <typename T>
class Widget{...}; //包括成员函数swap
template <typename T>
void swap(Widget<T> &a, Widget<T> &b)
{
a.swap(b);
}
}
这样一来,c++的名称查找法则会找到WidgetStuff内的Widget专属版本。
这个做法既适用于class也适用于template class,但你最好还是为swap函数在命名空间std中实现一个特化的版本:
template <typename T>
void DoSomething(T &a, T &b)
{
...
using std::swap;
swap(a, b);
...
}
如果这样调用swap函数,那么如果编译器找不到针对T在某个命名空间内的专属版本,就会调用std内的默认版本,但是如果这样调用:
template <typename T>
void DoSomething(T &a, T &b)
{
...
std::swap(a, b);
...
}
那么就会直接调用std中的默认版本, 所以为了使这种错误的调用方式也可以调用适当的swap函数,你可以在std中实现一个全特化的版本,这样编译器会首先调用全特化版本。
总结:
如果std版本的效率满足你的要求,可以什么都不做,否则:
1. 在类中提供一个public function高效的置换;
2. 在class或template class所在的命名空间提供一个non-member函数去调用成员函数;
3. 如果你编写的是class而不是template class,为你的class特化std::swap。
最后,注意函数的调用方式。
还有一点:成员版swap函数不应该抛出异常。