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,自動就是異常安全和自賦值安全的。


別人的答案:因爲在賦值時不涉及動態內存的分配所以沒有什麼受益

但是我覺得可以受益,雖然重新創建一個臨時的對象代價很小,此時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;
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章