标准库swap函数
我们首先看一下标准库的swap函数是如何实现的,它是一个定义在std命名空间的函数模板:
namespace std {
template<typename T>
void swap(T &a,T &b) {
T temp(a);
a = b;
b = temp;
}
}
可以看到,标准库的swap实现调用了拷贝构造函数(对于非内置类型),并且有两次的赋值运算,这在很多情况下是不满足我们的效率需求的(大型对象的拷贝是毫无必要的操作,我们仅需要交换指针,若存在容器类型,调用容器类的swap明显是更加正确的操作)。在C++11中,我们有如下的优化:
template<typename T>
void swap(T& a,T&b) {
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
移动方式的本质就是移交临时对象对资源的控制权,通常就是指针的替换,因此上述操作对存在移动构造和移动赋值运算的类来讲,已经可以基本满足要求,但是,对于未定义上述操作的类来讲,改进版本的swap操作并未有任何效率上的提升,因此,有必要定义类类型的swap。
copy and swap中的 swap操作
前一篇文章中关于copy and swap操作中提到了swap操作,作为成员函数的swap交换了两者指针:
class A {
private:
int *a;
public:
void swap(A& rhs) {
using std::swap;
swap(this->a,rhs.a);// 我们简单地交换了指针
}
};
我们稍后将解释关于using std::swap为何是必要的。
上述操作并没有解决问题,我们希望能够像调用普通swap函数操作一样调用swap(A &a,A&b),因此,我们下一步的操作就是在std命名空间内特化swap版本:
namespace std {
template<>
void swap<A>(A&a,A&b) {
a.swap(b);
}
}
在std空间内的特化版本满足C++标准的规定,这种扩充操作使得我们的特化版本对包含了std空间的文件都处于可见状态,因此,我们可以像以前一样使用swap进行交换:
using std::swap;
A a,b;
swap(a,b);
我们知道,C++允许对类模板全特化,但是对函数模板不允许全特化:
template<typename T>
// 以下定义不允许!
void swap<A<T>> (A<T>& a,A<T>& b) {
a.swap(b);
}
同样的,std内对swap的重载也不符合规定,我们的解决方案就是,在自定义的命名空间内定义swap函数:
namespace Astuff {
template<typename T>
class A {
...
...
};
template<typename T>
void swap(A<T>&a,A<T> &b) {
a.swap(b);
}
};
这样,我们在当前的命名空间内就拥有了swap的完整定义,那么为什么在使用时要加using std::swap呢?这句声明会使std命名空间的swap暴露出来,编译器会自动在当前命名空间和std空间内寻找最符合当前函数调用的swap版本,因此,以下的写法完全错误:
A a,b;
std::swap(a,b);
这种写法直接调用了std空间内的swap函数,因此并不符合大多数情况下的需求,using版本才是最准确的版本:
using std::swap;
swap(a,b);