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

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

swap函数在C++中是一个非常重要的函数,但实现也非常复杂。

看一个缺省的std::swap函数的实现

 namespace std {
	 template<typename T>
	 void swap( T& a , T& b)
	 {
		 T temp(a);
		 a = b;
		 b = temp
	 }
 }

①内置类型的调用

	int a = 2;
	int b =3;
	std::swap(a, b);
	cout<<"a:"<<a<<" b:"<<b<<endl;

②非内置类型的调用

自我定义的类类型需要实现拷贝构造函数和拷贝赋值函数才能执行swap。

swap的实现:a先调用拷贝构造函数创建temp,通过拷贝赋值函数将b赋给a,再通过赋值函数将temp赋给b;

但对于另一种类的定义:"以指针指向一个对象,内含真正数据"那种类型,这种设计的常见表现形式是所谓"pimpl 手法",即把数据寄托在另外一个类中。如果采用默认的swap函数,会出现什么状况?

class WigetImpl
{
public:
	WigetImpl(const int v) : m_value(v){ }
	int GetValue() const {return m_value;}
private:
	int m_value;
};

class Wiget
{
public:
	Wiget(const int v) {m_impl = new WigetImpl(v);}
	~Wiget() {if (m_impl != NULL) delete m_impl; m_impl = NULL;}

	Wiget(const Wiget &wiget)
	{
		this->m_impl = new WigetImpl(wiget.m_impl->GetValue());
	}

	Wiget& operator = (const Wiget &wiget)
	{
		if (this != &wiget) {   //采用copy-and-swap技术会更好,参考前面条款
			*m_impl = *wiget.m_impl;
		}

		return *this;
	}
private:
	WigetImpl *m_impl;
};

调用

	Wiget w1(2);
	Wiget w2(3);
	std::swap(w1, w2);

调用缺省的swap,不但需要调用三次Wiget的copy函数,还调用三次WigetImpl的copy函数,效率非常低。

我们希望能够告诉std:: swap: 当Widgets 被置换时真正该做的是置换其内部的m_impl指针。确切实践这个思路的一个做法是将std: :swap 针对Widget 特化,即全特化。

namespace std
{
	template<>
	void swap<Wiget>(Wiget &a,Wiget &b)
	{
		a.swap(b);  //调用Wiget的swap函数
	}
} 

在Wiget中定义一个swap函数,实现对m_impl指针交换

	void swap(Wiget & rhl)
	{
		std::swap(this->m_impl, rhl.m_impl);
	}

这样调用只是调用三次WigetImpl的copy函数。

假设Wiget和WigetImpl都是一个类模板,如下面

template<typename T>
class WigetImpl {};
template<typename T>
class Wiget{};

我们对函数进行偏特化

namespace std
{
	template<typename T>
	void swap<Wiget<T> >(Wiget<T> &lhs, Wiget<T> &rhs)
	{
		lhs.swap(rhs);
	}
}

但C++只对类偏特化,函数会报错,如:“std::swap”: 非法使用显式模板参数

解决函数偏特化问题,即修改为函数重载,如下:

namespace std
{
	template<typename T>
	void swap(Wiget<T> &lhs, Wiget<T> &rhs)
	{
		lhs.swap(rhs);
	}
}

但std是个特殊的命名空间,可以全特化std内的template,但不能添加新的template到std。

这些,都可以通到定义一个no-member function解决,不属于std的命名空间

template<typename T>
void swap(Wiget<T> &lhs, Wiget<T> &rhs)
{
	lhs.swap(rhs); //调用Wiget的swap函数
}

上面介绍了3中swap:

①std的缺省swap

②std的特化版本

③class命名空间的no-member swap

那么它们的调用顺序是怎么样呢?为了保证调用顺序是③②①,需要在函数中声明std命名空间

template<typename T>
void CallSwap(T &obj1, T &obj2)
{
<span style="white-space:pre">	</span>using std::swap;
<span style="white-space:pre">	</span>swap(obj1, obj2);
}

相关的原理,可以查看C++名称查找法则

总结swap的使用规则:

首先,如果swap的缺省实现为你的类或类模板提供了可接受的性能,你不需要做任何事。任何试图交换类型的对象的操作都会得到缺省版本的支持,而且能工作得很好。

第二,如果swap缺省实现效率不足(这几乎总是意味着你的类或模板使用了某种pimpl手法),就按照以下步骤来做:

1.提供一个public的swap成员函数,能高效地交换你的类型的两个对象值,这个函数应该永远不会抛出异常。

2.在你的类或模板所在的同一个namespace中,提供一个非成员的swap,用它调用你的swap成员函数。

3.如果你写了一个类(不是类模板),为你的类特化std::swap,并令它调用你的swap 成员函数。

最后,如果你调用swap,确保在你的函数中包含一个using 声明式使std::swap可见,然后在调用swap时不使用任何namespace修饰符

绝不要让swap的成员版本抛出异常。这是因为swap非常重要的应用之一是为类(以及类模板)提供强大的异常安全(exception-safety)保证。

贴出最终的测试代码

template<typename T>
class WigetImpl
{
public:
	WigetImpl(const T v) : m_value(v){ }
	T GetValue() const {return m_value;}
private:
	T m_value;
};

template<typename T>
class Wiget
{
public:
	Wiget(const T v) {m_impl = new WigetImpl<T>(v);}
	~Wiget() {if (m_impl != NULL) delete m_impl; m_impl = NULL;}

	Wiget(const Wiget &wiget)
	{
		this->m_impl = new WigetImpl<T>(wiget.m_impl->GetValue());
	}

	Wiget& operator = (const Wiget &wiget)
	{
		if (this != &wiget) {   //采用copy-and-swap技术会更好,参考前面条款
			*m_impl = *wiget.m_impl;
		}

		return *this;
	}

	void swap(Wiget & rhl)
	{
		std::swap(this->m_impl, rhl.m_impl);
	}

private:
	WigetImpl<T> *m_impl;
};

namespace std
{
	template<>
	void swap<Wiget<int> >(Wiget<int> &a,Wiget<int> &b)
	{
		a.swap(b);  //调用Wiget的swap函数
	}
} 

template<typename T>
void swap(Wiget<T> &lhs, Wiget<T> &rhs)
{
	lhs.swap(rhs);
}

template<typename T>
void CallSwap(T &obj1, T &obj2)
{
	using std::swap;
	swap(obj1, obj2);
}

记住

①当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常.

②如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于classes(而非template),

也请特化std::swap.

③调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何""命名空间资格修饰.

④为"用户定义类型"进行std templates全特化是好的,但千万不要尝试在std内加入某些std而言全新的东西.

条款25:考虑写出一个不抛异常的swap函数,布布扣,bubuko.com

时间: 2024-12-14 18:44:29

条款25:考虑写出一个不抛异常的swap函数的相关文章

EC读书笔记系列之13:条款25 考虑写出一个不抛异常的swap函数

记住: ★当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定其不抛出异常 ★若你提供一个member swap,也该提供一个non-member swap来调用前者.对于classes(而非templates),也请特化std::swap ★调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰” ★为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西 --

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

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常 示例: stl里的swap算法 namespace std{ template<typename T> void swap(T &a, T &b){ T temp(a); a = b; b = temp; } } //"pimpl手法"(pointer

《Effective C++》item25:考虑写出一个不抛异常的swap函数

std::swap()是个很有用的函数,它可以用来交换两个变量的值,包括用户自定义的类型,只要类型支持copying操作,尤其是在STL中使用的很多,例如: int main(int argc, _TCHAR* argv[]) { int a[10] = {1,2,3,4,5,6,7,8,9,10}; vector<int> vec1(a, a + 4); vector<int> vec2(a + 5, a + 10); swap(vec1, vec2); for (int i =

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

(一) 缺省情况下swap动作可由标准程序库提供的swap算法完毕: namespace std { template<typename T> void swap(T& a, T& b) { T temp(a); a = b; b = temp; } } 这个函数是异常安全性编程的核心,而且是用来处理自我赋值可能性的一个常见机制 可是对某些类型而言,这些复制动作无一必要:当中基本的就是"以指针指向一个对象,内含真正数据"那种类型.多为"pimpl手

[Effective C++ --025]考虑写出一个不抛异常的swap函数

引言 在我的上一篇博客中,讲述了swap函数. 原本swap只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性. 一.swap函数 标准库的swap函数如下: 1 namespace std { 2 template<typename T> 3 void swap(T &a, T& b) 4 { 5 T temp(a); 6 a = b; 7 b = temp; 8 } 9 } 只要类型T支持拷贝(通过拷贝构造函数和拷贝赋值操作符完成),那么这个版本的

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

当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常. 如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于class(而非templates),也请特化std::swap. 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”. 为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西.

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

1. swap是STL的一部分,后来成为异常安全性编程(exception-safe programming)(见条款29)的一个重要脊柱,标准库的swap函数模板定义类似以下: namespace std{ template<typename T> swap(T& lhs,T& rhs){ T temp(lhs); lhs=rhs; rhs=temp; } } 只要T类型支持拷贝构造以及拷贝赋值,标准库swap函数就会调用T的拷贝构造函数和拷贝构造操作符完成值的转换,但对于某

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

1 // lib中的swap 2 namespace std { 3 template<typename T> 4 void swap (T& a, T& b) 5 { 6 T temp(a); 7 a = b; 8 b = temp; 9 } 10 } 11 12 // 缺点:需要赋值大量的数据,但是有的时候并不要复制如此多的内容 13 class WidgetImpl { 14 public: 15 //... 16 private: 17 int a, b, c; 18

《Effective C 》资源管理:条款25--考虑写出一个不抛出异常的swap函数

条款25考虑写出一个不抛出异常的swap函数 条款25:考虑写出一个不抛出异常的swap函数 swap是STL中的标准函数,用于交换两个对象的数值.后来swap成为异常安全编程(exception-safe programming,条款29)的脊柱,也是实现自我赋值(条款11)的一个常见机制.swap的实现如下: namespace std{ template<typename T> void swap(T& a, T& b) { T temp(a); a=b; b=temp;