C++中Copy-Swap實現(xiàn)拷貝交換
正式介紹 Copy-Swap 之前,先看下《劍指 Offer》里的第??題:
如下為類型 CMyString 的聲明,請為該類型添加賦值運算符函數(shù)。
class CMyString { public: CMyString(char* pData = nullptr); CMyString(const CMyString& str); ~CMyString(); private: char* m_pData; };
這道題目雖然基礎(chǔ),但考察點頗多,有區(qū)分度:
- 返回值類型應(yīng)為引用類型,否則將無法支持形如
s3 = s2 = s1
的連續(xù)賦值 - 形參類型應(yīng)為 const 引用類型
- 無資源泄露,正確釋放賦值運算符左側(cè)的對象的資源
- 自賦值安全,能夠正確處理
s1 = s1
的語句 - 考慮異常安全
解法 1
CMyString& operator=(const CMyString& str) { if(this == &str) return *this; delete[] m_pData; m_pData = nullptr; m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData, str.m_pData); return *this; }
上面代碼有些細(xì)節(jié)需要注意:
- 刪除數(shù)組使用
delete[]
運算符 strlen
計算長度不含字符串末尾的結(jié)束符\0
strcpy
會拷貝結(jié)束符\0
解法 1 滿足考察點中除異常安全外的所有要求:new 的時候可能由于內(nèi)存不足拋異常,但此時賦值運算符左側(cè)的的對象已被釋放,m_pData 為空指針,導(dǎo)致左側(cè)對象處于無效狀態(tài)。
解決方案:只要先 new 分配空間,再 delete 釋放原來的空間即可。這樣可以保證即使 new 失敗拋異常,賦值運算符左側(cè)對象也尚未修改,仍處于有效狀態(tài)。
解法 2
《劍指 Offer》中給出了更好的解法:先創(chuàng)建賦值運算符右側(cè)對象的一個臨時副本,然后交換賦值運算符左側(cè)對象和該臨時副本的 m_pData,當(dāng)臨時對象 strTemp 離開作用域時,自動調(diào)用其析構(gòu)函數(shù),釋放 m_pData 指向的資源(即賦值運算符左側(cè)對象原來的內(nèi)存):
CMyString& operator=(const CMyStirng& str) { if(this != &str) { CMyString strTemp(str); char* pTemp = m_pData; m_pData = strTemp.m_pData; strTemp.m_pData = pTemp; } return *this; }
解法 2 巧妙地利用了類原本的拷貝構(gòu)造、析構(gòu)函數(shù)自動進(jìn)行資源管理,同時又不涉及底層的 new[]/delete[] 操作,可讀性更強,也不容易出錯。
解法 2 是 Copy-Swap 的雛形。C++ 中管理資源類通常會定義自己的 swap 函數(shù),與其他拷貝控制成員(拷貝/移動構(gòu)造、拷貝/移動賦值運算符、析構(gòu))不同,swap 不是必須,但卻是重要的優(yōu)化手段,以下是使用 Copy-Swap 慣用法的解法:
解法 3
class CMyString { friend void Swap(CMyString& lhs, CMyString& rhs) noexcept { // 對 CMyString 的成員逐一交換 std::swap(lhs.m_pData, rhs.m_pData); } // ... }; CMyString(CMyString&& str) : CMyString() { Swap(*this, str); } CMyString& operator=(CMyStirng str) { Swap(*this, str); return *this; }
這里有幾點需要注意:
- 拷貝賦值運算符的形參類型不再是 const 引用,因為 Copy-Swap 需要先對賦值運算符右側(cè)對象進(jìn)行拷貝,這里直接使用值傳遞。這樣一來,也使得 Copy-Swap 天然地異常安全、自賦值安全。
- 異常安全:進(jìn)入函數(shù)
operator=()
之前,先進(jìn)行拷貝 - 自賦值安全:形參是一個新創(chuàng)建的臨時對象,永遠(yuǎn)不可能是對象自身
- 異常安全:進(jìn)入函數(shù)
- 不需要額外實現(xiàn)移動賦值運算符:如果賦值運算符右側(cè)是一個右值,則自動調(diào)用 CMyString 的移動構(gòu)造來構(gòu)造形參
這還沒完...
標(biāo)準(zhǔn)庫 std::swap 及 ADL
C++ 標(biāo)準(zhǔn)庫也提供了 swap 函數(shù),理論上需要一次拷貝,兩次賦值:
void swap(CMyString& lhs, CMyString& rhs) { CMyString tmp(lhs); lhs = rhs; rhs = tmp; }
其中 CMyString tmp(lhs)
會調(diào)用 CMyString 的拷貝構(gòu)造進(jìn)行深拷貝,效率上不如 CMyString 類自己實現(xiàn)的直接交換指針的效率高。
在進(jìn)行 swap(v1, v2) 的調(diào)用時,如果類實現(xiàn)了自己的 swap 版本,其匹配程度優(yōu)于標(biāo)準(zhǔn)庫的版本。如果類沒有定義自己的 swap,則使用標(biāo)準(zhǔn)庫的 swap。這種查找匹配方式被稱為 ADL(Argument-Dependent Lookup)。
注意不能使用 std::swap 形式,因為這樣會強制使用標(biāo)準(zhǔn)庫的 swap。正確的做法是提前使用 using std::swap
聲明,而后續(xù)所有的 swap 都應(yīng)該是不加限制的(這一點剛好和 std::move 相反):
void swap(Bar& lhs, Bar& rhs) { using std::swap; swap(lhs.m1, rhs.m1); swap(lhs.m2, rhs.m2); swap(lhs.m3, rhs.m3); }
最終的結(jié)果
class CMyString { friend void swap(CMyString& lhs, CMyString& rhs) noexcept { // 對 CMyString 的成員逐一交換 using std::swap; swap(lhs.m_pData, rhs.m_pData); } // ... }; CMyString(CMyString&& str) : CMyString() { swap(*this, str); } CMyString& operator=(CMyStirng str) { swap(*this, str); return *this; }
到此這篇關(guān)于C++中Copy-Swap實現(xiàn)拷貝交換的文章就介紹到這了,更多相關(guān)C++ Copy-Swap拷貝交換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VSCode 搭建 Arm 遠(yuǎn)程調(diào)試環(huán)境的步驟詳解
這篇文章主要介紹了VSCode 搭建 Arm 遠(yuǎn)程調(diào)試環(huán)境的步驟詳解,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04VS2019編寫C程序或者CUDA程序出現(xiàn)“無法啟動程序,系統(tǒng)找不到指定的文件”問題的詳細(xì)解決方法
這篇文章主要介紹了VS2019編寫C程序或者CUDA程序出現(xiàn)“無法啟動程序,系統(tǒng)找不到指定的文件”問題的詳細(xì)解決方法,文中通過圖文的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08C++實現(xiàn)LeetCode(30.串聯(lián)所有單詞的子串)
這篇文章主要介紹了C++實現(xiàn)LeetCode(30.串聯(lián)所有單詞的子串),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07