C++ Primer 第十三章 13.3 交换操作 练习和总结

13.3 交换操作

对于管理资源的类(我的理解是数据成员有动态分配的内存的类),一般需要定义一个swap函数。

默认的swap是这样的:

A temp = v1;
v1=v2;
v2=temp;

代码会创建一个临时变量,并且使用两次赋值语句。

想一下,如果一个对象中有一个指针指向了一个占用内存非常大的对象。这样创建临时变量是比较消耗性能的。

**所以不创建临时变量,而是只交换两个对象内部的数据。**如练习13.30的swap所示

这样一来可以提高效率,当然 ,这不是必须的,只是一种优化的手段

如果我们定义为类定义了自己版本的swap,则那些需要使用swap的算法,会优先调用我们自己定义的swap。

在赋值运算符中使用swap

在之前写的代码中,可以了解到赋值运算符中需要同时完成析构和拷贝构造的工作,但是人可能会粗心而忘记写一部分。

使用swap可以不用担心这样的问题。如下图所示
在这里插入图片描述
我们需要改变的是讲原来的引用形参变为非引用类型。此时交换两个对象之后,rhs是调用函数的实参的副本,在函数调用结束之后会调用析构函数。
而*this则保存了之前rhs的对象。

这样我们不需要考虑知否是自赋值以及考虑是否需要释放*this对象的指针类型数据成员所指向的对象。

唯一的缺点可能是,需要创建一个非引用类型的形参。

练习

13.29

因为swap函数中对swap函数的调用不是同一个函数而是重载了的函数,这在本质上和swap中调用一个普通函数没有区别

13.30

由于之前写的HasPtr没有经过测试所以忘了把s传入到new string()中了,现在已经修正

函数在一些需要移动元素的算法中会 被调用。

class HasPtr {
	friend void swap(HasPtr& obj1, HasPtr& obj2);
public:
	HasPtr(const std::string& s = std::string()) :ps(new std::string(s)), i(0) {};
	HasPtr(const HasPtr& hasptr) :ps(new string(*hasptr.ps)), i(hasptr.i) {

	};
	//列表初始化只能在构造函数中使用
	HasPtr& operator=(const HasPtr& p) {
		auto temp = new string(*p.ps);
		delete ps;
		ps = temp;
		i = p.i;
		return *this;
	};
	~HasPtr() {
		delete ps;
	}
	string desc() {
		std::ostringstream str("");
		str << "ps:" << *ps << ",i:" << i;
		return str.str();
	}
private:
	std::string *ps;
	int i;
};
void swap(HasPtr& obj1, HasPtr& obj2) {
	using std::swap;
	swap(obj1.ps,obj2.ps);
	swap(obj1.i,obj2.i);
	cout << "HasPtr::swap(HasPtr& obj1, HasPtr& obj2)" << endl;
}
13.31

下面的排序算法并没有调用swap函数,根据知乎大神的解释,这是由于容器中的参数较少,sort使用的是插入排序所以没有调用swap函数,如果元素较多则会调用快速排序,此时会调用swap;

知乎的解释

vector<HasPtr> vec = {string("adsf"),string("sdf"),string("jocl"),string("123"),string("sdf")};
	std::sort(vec.begin(), vec.end());
	for (const auto& item:vec) {
		cout << item.desc() << endl;
	}

下面的代码添加了1000个元素,此时调用了swap函数

vector<HasPtr> vec = {string("adsf"),string("sdf"),string("jocl"),string("123"),string("sdf")};
	for (int i = 0; i < 1000;++i) {
		vec.push_back(string("asd"));
	}
	
	std::sort(vec.begin(), vec.end());
13.32

别人的答案:因为在赋值时不涉及动态内存的分配所以没有什么受益

但是我觉得可以受益,虽然重新创建一个临时的对象代价很小,此时swap受益不大,但是如果我们在赋值语句中使用了swap,那么可以显著的减少代码量,而且代码是异常安全的。

这里的异常安全,讲的是在修改一个对象时,如果发生了异常,程序员自己应该怎么做让数据不会紊乱。

我的理解一个是,我们可能会发生写delete,但是使用swap函数,我们不用考虑这个,第二个是swap中没有new,所以不会发生异常。所以在swap中,代码是异常安全的。(估计理解不对,还需要多实践才能明白异常安全是什么意思)

比如下面的代码改写成:

HasPtr& operator=(const HasPtr& p) {
	++*p.ref_count;
	if (--*ref_count==0) {
		delete ps;
		delete ref_count;
	}
	ps = p.ps;
	ref_count = p.ref_count;
	i = p.i;
	return *this;
};

**这样子,可以看到代码显著的减少了,而且可以避免我们在coding的时候忘记delete对象。
因为p在函数执行完成之后,会调用析构函数。 **

当然这里的形参变成了非引用的形式需要执行一次拷贝,从性能的角度来看确实没有什么受益。但是从代码简洁重用的角度来看,还是受益了呀

HasPtr& operator=(HasPtr p) {
	swap(*this, p);
	return *this;
};
发布了54 篇原创文章 · 获赞 6 · 访问量 3301

猜你喜欢

转载自blog.csdn.net/zengqi12138/article/details/104498855