c++ 移動(dòng)賦值/移動(dòng)構(gòu)造函數(shù)的實(shí)現(xiàn)
在 C++11 引入移動(dòng)語(yǔ)義(Move Semantics)之前,對(duì)象之間的賦值或初始化通常涉及深拷貝(Deep Copy),即復(fù)制所有數(shù)據(jù)。這對(duì)于包含動(dòng)態(tài)分配資源(如 std::vector, std::string, 原始指針管理的內(nèi)存)的對(duì)象來(lái)說(shuō),開(kāi)銷很大。
移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符允許我們將資源的“所有權(quán)”從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象,而不是復(fù)制數(shù)據(jù)。這極大地提高了性能,特別是對(duì)于臨時(shí)對(duì)象(右值)。
1. 核心概念:左值 vs 右值
理解移動(dòng)語(yǔ)義的前提是區(qū)分左值(lvalue)和右值(rvalue):
- 左值:有名字、有持久地址的對(duì)象(如
int a = 10;中的a)。 - 右值:通常是臨時(shí)對(duì)象、字面量或即將銷毀的對(duì)象(如
a + b的結(jié)果,或者函數(shù)返回的非引用對(duì)象)。
移動(dòng)語(yǔ)義的核心思想是:既然右值(臨時(shí)對(duì)象)馬上就要銷毀了,我們沒(méi)必要復(fù)制它的資源,直接“偷”過(guò)來(lái)用就行了。
2. 移動(dòng)構(gòu)造函數(shù) (Move Constructor)
定義:當(dāng)一個(gè)新對(duì)象被一個(gè)右值初始化時(shí)調(diào)用。
簽名:ClassName(ClassName&& other)
參數(shù):接受一個(gè)右值引用(T&&)。
作用
將源對(duì)象(other)的資源指針直接轉(zhuǎn)移給新對(duì)象,并將源對(duì)象的指針置為 nullptr(或其他安全狀態(tài)),防止析構(gòu)時(shí)重復(fù)釋放內(nèi)存。
代碼示例
class MyVector {
private:
int* data;
size_t size;
public:
// 構(gòu)造函數(shù)
MyVector(size_t s) : size(s) {
data = new int[s];
std::cout << "構(gòu)造: 分配內(nèi)存\n";
}
// 拷貝構(gòu)造函數(shù) (深拷貝)
MyVector(const MyVector& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
std::cout << "拷貝構(gòu)造: 深拷貝內(nèi)存\n";
}
// --- 移動(dòng)構(gòu)造函數(shù) ---
MyVector(MyVector&& other) noexcept
: data(other.data), size(other.size) {
// 關(guān)鍵步驟:竊取指針
other.data = nullptr; // 將源對(duì)象置為空,防止其析構(gòu)時(shí)釋放這塊內(nèi)存
other.size = 0;
std::cout << "移動(dòng)構(gòu)造: 轉(zhuǎn)移所有權(quán)\n";
}
// 析構(gòu)函數(shù)
~MyVector() {
if (data) {
delete[] data;
std::cout << "析構(gòu): 釋放內(nèi)存\n";
}
}
};
// 使用場(chǎng)景
MyVector createVector() {
MyVector temp(100);
return temp; // 返回臨時(shí)對(duì)象(右值)
}
int main() {
// 這里會(huì)觸發(fā)移動(dòng)構(gòu)造函數(shù),而不是拷貝構(gòu)造
MyVector v = createVector();
return 0;
}3. 移動(dòng)賦值運(yùn)算符 (Move Assignment Operator)
定義:當(dāng)一個(gè)已存在的對(duì)象被賦予一個(gè)右值時(shí)調(diào)用。
簽名:ClassName& operator=(ClassName&& other)
返回值:通常返回 *this 以支持鏈?zhǔn)劫x值。
作用
- 自賦值檢查:防止
a = std::move(a)導(dǎo)致錯(cuò)誤。 - 清理舊資源:釋放當(dāng)前對(duì)象持有的舊資源。
- 竊取新資源:接管源對(duì)象的資源。
- 重置源對(duì)象:將源對(duì)象置于有效但未指定的狀態(tài)(通常指針置空)。
代碼示例
class MyVector {
// ... (同上) ...
public:
// --- 移動(dòng)賦值運(yùn)算符 ---
MyVector& operator=(MyVector&& other) noexcept {
if (this != &other) { // 1. 自賦值檢查
// 2. 釋放當(dāng)前資源
delete[] data;
// 3. 竊取資源
data = other.data;
size = other.size;
// 4. 重置源對(duì)象
other.data = nullptr;
other.size = 0;
}
std::cout << "移動(dòng)賦值: 轉(zhuǎn)移所有權(quán)\n";
return *this;
}
};
int main() {
MyVector v1(10);
MyVector v2(20);
// std::move 將左值 v1 強(qiáng)制轉(zhuǎn)換為右值引用,觸發(fā)移動(dòng)賦值
v2 = std::move(v1);
// 此時(shí) v1 內(nèi)部指針為 null,v2 擁有了原本 v1 的內(nèi)存
return 0;
}4. 關(guān)鍵細(xì)節(jié)與最佳實(shí)踐
A.noexcept的重要性
移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符必須標(biāo)記為 noexcept(除非你真的可能拋出異常)。
- 原因:標(biāo)準(zhǔn)庫(kù)容器(如
std::vector)在擴(kuò)容重新分配內(nèi)存時(shí),如果元素的移動(dòng)操作是noexcept的,它會(huì)優(yōu)先使用移動(dòng);否則,為了保證異常安全(如果移動(dòng)中途失敗,原數(shù)據(jù)還在),它只能退回到拷貝。 - 如果不加
noexcept,std::vector<MyVector>的性能可能會(huì)退化回拷貝語(yǔ)義。
B.std::move是什么?
std::move 不移動(dòng)任何東西。它只是一個(gè)強(qiáng)制類型轉(zhuǎn)換工具,將左值轉(zhuǎn)換為右值引用(T&&),從而告訴編譯器:“這個(gè)對(duì)象我可以被移動(dòng),請(qǐng)調(diào)用移動(dòng)版本的操作”。
- 調(diào)用
std::move(x)后,x處于有效但未指定的狀態(tài)。除了銷毀或重新賦值外,不應(yīng)再使用x的值。
C. 規(guī)則之五 (Rule of Five)
如果你需要自定義以下五個(gè)函數(shù)中的任何一個(gè),通常意味著你需要自定義全部五個(gè):
- 析構(gòu)函數(shù)
- 拷貝構(gòu)造函數(shù)
- 拷貝賦值運(yùn)算符
- 移動(dòng)構(gòu)造函數(shù)
- 移動(dòng)賦值運(yùn)算符
如果你只定義了移動(dòng)操作而沒(méi)有定義拷貝操作,編譯器會(huì)自動(dòng)刪除默認(rèn)的拷貝構(gòu)造和拷貝賦值函數(shù)(因?yàn)橘Y源已經(jīng)被移走了,默認(rèn)拷貝是不安全的)。
D. 成員變量的自動(dòng)移動(dòng)
如果你的類成員變量本身支持移動(dòng)語(yǔ)義(如 std::unique_ptr, std::vector, std::string),你可以使用 default 讓編譯器自動(dòng)生成高效的移動(dòng)操作:
class MyClass {
std::vector<int> vec;
std::string name;
// 編譯器自動(dòng)生成的移動(dòng)構(gòu)造/賦值會(huì)分別調(diào)用 vec 和 name 的移動(dòng)操作
// 非常高效且安全
MyClass(MyClass&&) = default;
MyClass& operator=(MyClass&&) = default;
};總結(jié)對(duì)比
| 特性 | 拷貝構(gòu)造/賦值 | 移動(dòng)構(gòu)造/賦值 |
|---|---|---|
| 參數(shù)類型 | const T& (左值引用) | T&& (右值引用) |
| 行為 | 深度復(fù)制數(shù)據(jù) (Deep Copy) | 轉(zhuǎn)移資源所有權(quán) (Steal Pointers) |
| 源對(duì)象狀態(tài) | 保持不變 | 變?yōu)橛行У粗付顟B(tài) (通常為空) |
| 性能 | 較慢 (涉及內(nèi)存分配和復(fù)制) | 極快 (僅指針賦值) |
| 適用場(chǎng)景 | 需要保留源對(duì)象數(shù)據(jù)時(shí) | 源對(duì)象是臨時(shí)值或不再需要時(shí) |
到此這篇關(guān)于c++ 移動(dòng)賦值/移動(dòng)構(gòu)造函數(shù)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)c++ 移動(dòng)賦值/移動(dòng)構(gòu)造內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)查詢本機(jī)信息的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用C++實(shí)現(xiàn)查詢本機(jī)信息,并且進(jìn)行上報(bào),文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2023-05-05
OpenCV利用高斯模糊實(shí)現(xiàn)簡(jiǎn)單的磨皮美顏效果
這篇文章主要介紹了通過(guò)OpenCV中的高斯模糊以及雙邊模糊來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的磨皮美顏效果,文中的講解很詳細(xì),感興趣的同學(xué)可以學(xué)習(xí)一下2021-12-12
C++中inet_pton、inet_ntop函數(shù)的用法
這篇文章主要介紹了C++中inet_pton、inet_ntop函數(shù)的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
關(guān)于C++中由于字節(jié)對(duì)齊引起內(nèi)存問(wèn)題定位分析
前幾天遇到一個(gè)稀奇古怪的問(wèn)題,在創(chuàng)建對(duì)象的時(shí)候程序異常退出,查找代碼發(fā)現(xiàn)結(jié)構(gòu)體數(shù)組問(wèn)題,最終把問(wèn)題簡(jiǎn)化得到解決方法,下面小編把我的問(wèn)題及解決方案分享到腳本之家平臺(tái)供大家參考下2021-06-06
C語(yǔ)言接口與實(shí)現(xiàn)方法實(shí)例詳解
這篇文章主要介紹了C語(yǔ)言接口與實(shí)現(xiàn)方法,包括接口的概念、實(shí)現(xiàn)方法及抽象數(shù)據(jù)類型等,并配合實(shí)例予以說(shuō)明,需要的朋友可以參考下2014-09-09
C語(yǔ)言用數(shù)組實(shí)現(xiàn)反彈球消磚塊
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言用數(shù)組實(shí)現(xiàn)反彈球消磚塊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
C語(yǔ)言近萬(wàn)字為你講透樹(shù)與二叉樹(shù)
樹(shù)是計(jì)算機(jī)算法最重要的非線性結(jié)構(gòu)。因?yàn)闃?shù)能很好地描述結(jié)構(gòu)的分支關(guān)系和層次特性,所以在計(jì)算機(jī)科學(xué)和計(jì)算機(jī)應(yīng)用領(lǐng)域有著廣泛的應(yīng)用。這篇文章我就帶大家一起了解一下樹(shù)、二叉樹(shù)這種結(jié)構(gòu),下篇文章會(huì)重點(diǎn)向大家介紹二叉樹(shù)的遍歷算法2022-05-05
C++ OpenCV實(shí)現(xiàn)像素畫的示例代碼
這篇文章主要介紹了通過(guò)OpenCV進(jìn)行圖片像素的變化,從而形成像素畫效果的功能。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手試一試2022-01-01

