在默认情况下swap可由标准程序库提供的swap算法完成,典型的实现如下:
template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
考虑以下的类:
class Person
{
private:
int age;
string name;
public:
...
};
class Student
{
private:
Person * p;
public:
Student(const Student & rhs);
Student& operator=(const Student& rhs)
{
...
*p = *(rhs.p);
...
}
};
一旦要交换两个
Student
对象的值,我们唯一需要做的就是交换P指针.
但默认的swap算法不知道这一点,它不止复制三个
Student
,还复制三个
Person
对象。非常缺乏效率.
我们希望能够告诉
std::swap
当
Student
被交换时真正该做的是交换内部的p指针.
一个思路是:将
std::swap
针对
Student
特化。比如类似下面的代码:
namespace std
{
template<> void swap<Student>(Student & a, Student & b)
{
swap(a.p,b.p);
}
}
通常我们不能够改变std命名空间内的任何东西,但可以为标准的templates制造特化版本。
然而这个版本无法通过编译,因为p是私有成员,无法访问.
我们可以将这个特化版本声明为friend,但是和以前不一样:我们让
Student
声明一个为
swap
的
public
成员函数做真正的交换工作,然后将
std::swap
特化,让它调用这个成员函数:
class Student
{
public:
...
void swap(Student & other)
{
using std::swap;
swap(p,other.p);
}
};
namespace std
{
template<>void swap<Student>(Student & a, Student & b)
{
a.swap(b);
}
}
这样做不仅能通过编译,还与STL容器一致,因为所有STL容器也提供了
public swap
成员函数和
std::swap
特化版本.
然后当Person 和 Student都是类模板而不是类,也许我们可以试试将Student内的数据类型加以参数化:
template<typename T>
class Person{...};
template<typename T>
class Student{...};
swap成员函数我们想写成这样:
namespace std
{
template<typename T>void swap<Student<T>>(Student<T> & a, Student<T> & b)
{
a.swap(b);
}
}
看起来合情合理,却不合法。因为我们企图偏特化一个函数模板,但C++只允许对类模板偏特化。当你打算偏特化一个函数模板时,通常做法是简单地为它添加重载版本,像这样:
namespace std
{
template<typename T>void swap(Student<T> & a, Student<T> & b)
{
a.swap(b);
}
}
一般情况下,重载函数模板没有问题,但std是特殊命名空间,你可以全特化std内的模板,但不可用添加新的模板到std里面。
那怎么办?解决办法很简单:还是声明一个非成员的swap让它调用成员的swap,但不在std命名空间下进行了:
namespace Student
{
...
template<typename T>
class Student{...};
...
template<typename T>void swap(Student<T> & a, Student<T> & b)
{
a.swap(b);
}
}
现在任何地点的代码如果打算交换两个
Student
对象,因而调用
swap
,C++的查找法则会找到
Student
内的
Student
专属版本。
这个做法对类和类模板都行得通。
假设有以下的函数模板,其内需要交换两个对象值:
template<typename T>
void doSomething(T & obj1, T & obj2)
{
...
swap(obj1,obj2);
...
}
应该调用哪一个swap?是std一般化的版本?还是某个可能存在的特化本吧,或者是一个可能存在的T专属版本而且在某个命名空间内?
你希望的是调用T的专属版本,并且在该版本不存在的情况下调用std内的一般化版本,可以这样做:
template<typename T>
void doSomething(T & obj1, T & obj2)
{
using std::swap
...
swap(obj1,obj2);
...
}
C++的名称查找法则确保将找到全局作用域或T所在命名空间内的任何T专属的swap。如果没有T专属的swap存在,编译器就使用std内的swap.
需要注意的是,不要添加额外修饰符。假设你以以下方式调用swap:
这强迫编译器只认std内的swap
总结:
1.当std::swap对你的类型效率不高时,提供一个swap成员函数,并确保不会抛出异常
2.如果你提供一个成员swap,也应该提供一个非成员swap来调用前者。对于类,也请特化std::swap
3.调用swap时针对std::swap使用using声明式,然后调用swap并且不带任何命名空间修饰符
4.进行std模板全特化是好的,但不要尝试在std内加入新的东西