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;
};