天天看点

Effective C++条款25:考虑写出一个不抛异常的swap函数

在默认情况下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内加入新的东西

继续阅读