精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

左值?右值?&&是什么鬼?—— 寫給所有被C++移動語義折磨的人

開發(fā)
你是不是經(jīng)常看到C++代碼中那些奇怪的&、&&符號,還有到處亂飛的std::move,然后一臉懵逼?別擔(dān)心,今天我用大白話帶你徹底搞懂這些東西!

開場小段子:搬家引發(fā)的思考

想象一下,你要搬家。如果你是土豪,可能會直接買新家具,舊家具直接扔掉。但如果你像我一樣是個普通人,肯定是把家具從舊房子搬到新房子。

這就是C++移動語義的核心思想——與其復(fù)制一份資源,不如直接把資源的所有權(quán)轉(zhuǎn)移過去!

一、左值和右值: C++中最被誤解的概念

很多教材會告訴你:"等號左邊是左值,右邊是右值"。這種解釋就像告訴你"太陽從東邊升起"一樣,雖然看起來沒錯,但一旦情況復(fù)雜起來就不夠用了。

1. 左值和右值的本質(zhì)區(qū)別

最簡單實用的判斷標(biāo)準(zhǔn)是:

  • 能取地址的就是左值 —— 它在內(nèi)存中有確定位置
  • 不能取地址的就是右值 —— 它是臨時的,轉(zhuǎn)瞬即逝

舉些栗子感受一下:

// 左值例子:
int a = 42;        // a是左值,&a是合法的
int arr[5];        // arr是左值,&arr是合法的
int *p = &a;       // p是左值,&p是合法的
a = 100;           // a可以出現(xiàn)在等號左邊,是個"可修改的左值"
const int c = 10;  // c是左值但不可修改,是個"不可修改的左值"
// 右值例子:
int b = a + 1;     // a+1是右值,你不能寫&(a+1)
int d = 42;        // 字面量42是右值,你不能寫&42
func();            // 函數(shù)返回值是右值(除非返回引用)

2. 腦洞助記:左值像房子,右值像旅館

想象一下:

(1) 左值就像你擁有的房子:

  • 有固定地址(可以&取址)
  • 可以長期存在(生命周期確定)
  • 可以反復(fù)訪問(可以多次使用)
  • 可以改裝(可修改,除非const)

(2) 右值就像旅館房間:

  • 臨時的(生命周期短)
  • 住完就退房(用完就銷毀)
  • 地址無法長期持有(不能直接取址)
  • 東西可以被帶走(資源可以被轉(zhuǎn)移走,可以被右值引用捕獲)

二、引用:普通引用vs右值引用

1. 左值引用 (普通引用)

在傳統(tǒng)C++中,"引用"通常指的是左值引用:

int a = 42;
int& ref = a;  // 左值引用,綁定到左值
ref = 100;     // 修改ref實際上就是修改a
std::cout << a;  // 輸出100,原變量被修改了

// 下面這些是錯誤的用法
// int& invalid_ref;        // 錯誤:引用必須初始化  
// int& ref_to_literal = 42; // 錯誤:不能綁定到字面量(右值)

左值引用的幾個關(guān)鍵特點:

  • 必須初始化,而且一旦綁定就不能重新綁定到其他對象
  • 對引用的操作就是對原變量的操作
  • 常規(guī)左值引用只能綁定到左值(名字都帶"左值",當(dāng)然只能綁左值啦)

2. const左值引用的特殊性

const int&是個特殊的存在,它既能綁左值,又能綁右值!

int x = 42;
const int& ref1 = x;    // 綁定到左值,沒問題
const int& ref2 = 42;   // 綁定到右值,也沒問題!
const int& ref3 = x+1;  // 綁定到表達式結(jié)果,也可以!

為什么const int&能綁定右值?因為編譯器會創(chuàng)建一個臨時變量來存儲右值,然后引用綁定到這個臨時變量上。而且因為是const的,所以保證你不會修改這個臨時對象,安全!

這就是為什么你經(jīng)常看到函數(shù)參數(shù)用const T&——它能同時接受左值和右值參數(shù)!

void printValue(const std::string& s) {  // 既能接受左值又能接受右值
    std::cout << s << std::endl;
}

std::string str = "hello";
printValue(str);             // 左值:沒問題
printValue(str + " world");  // 右值:也沒問題!

3. 右值引用 (C++11新特性)

C++11引入了右值引用,語法是雙&&:

int&& rref = 42;       // 右值引用,綁定到右值
int x = 10;
// int&& bad_ref = x;  // 錯誤:右值引用不能直接綁定到左值

右值引用專門用來綁定右值的,這些右值通常是臨時的、即將消亡的值。

4. 右值引用的"雙面性"

這個很重要但容易讓人混亂:右值引用類型的變量本身是左值!

int&& rref = 42;  // rref的類型是右值引用(int&&),但rref本身是個左值
int& ref = rref;  // 正確!因為rref雖然類型是右值引用,但它是個有名字的變量,所以是左值

void foo(int&& x) {
    x = 100;  // x在函數(shù)內(nèi)部是左值!盡管它的類型是右值引用
}

記住這個規(guī)則:如果它有名字,它就是左值,不管它的類型是什么!

5. 左值引用和右值引用在函數(shù)中的表現(xiàn)

void foo(int& x) {
    // 參數(shù)必須是左值
}

void bar(int&& x) {
    // 參數(shù)必須是右值
    // 但x本身在函數(shù)內(nèi)部是左值(因為它有名字)
}

int main() {
    int a = 5;
    foo(a);     // 正確,a是左值
    // foo(10);    // 錯誤,10是右值
    
    bar(10);    // 正確,10是右值
    // bar(a);     // 錯誤,a是左值
}

6. 究竟什么時候用左值引用,什么時候用右值引用?

(1) 用左值引用的場景:

  • 想避免復(fù)制大對象時:void process(BigObject& obj);
  • 需要修改傳入的參數(shù)時:void increment(int& value);
  • 實現(xiàn)"輸出參數(shù)"時:void getValues(int& out1, std::string& out2);

(2) 用const左值引用的場景:

  • 想避免復(fù)制,但不需要修改原對象:void print(const BigObject& obj);
  • 函數(shù)既要接受左值又要接受右值:bool compare(const std::string& s1, const std::string& s2);

(3) 用右值引用的場景:

  • 實現(xiàn)移動語義(下面會講):void moveFrom(BigObject&& obj);
  • 完美轉(zhuǎn)發(fā)(高級話題,下次講):template<typename T> void wrapper(T&& param);

左值引用就像借用別人的東西,而右值引用則像是接管了一個無主之物!

三、移動語義:不是真的"移動",而是"偷"

現(xiàn)在我們來到C++11最激動人心的部分!移動語義就像是程序員的"循環(huán)利用"藝術(shù),讓我們能夠合法地"偷"資源,而不是復(fù)制它們。

1. 傳統(tǒng)復(fù)制的問題

假設(shè)你有個自定義字符串類:

class MyString {
private:
    char* data;
    size_t length;
public:
    // 構(gòu)造函數(shù)
    MyString(constchar* str) {
        length = strlen(str);
        data = newchar[length + 1];
        strcpy(data, str);
    }
    
    // 析構(gòu)函數(shù)
    ~MyString() {
        delete[] data;
    }
    
    // ... 其他成員
};

傳統(tǒng)的復(fù)制是這樣的:

// 復(fù)制構(gòu)造函數(shù)
MyString(const MyString& other) {
    length = other.length;
    data = newchar[length + 1];
    strcpy(data, other.data);  // 復(fù)制內(nèi)容,開辟新內(nèi)存
}

// 復(fù)制賦值運算符
MyString& operator=(const MyString& other) {
    if (this != &other) {
        delete[] data;  // 釋放原有資源
        length = other.length;
        data = newchar[length + 1];
        strcpy(data, other.data);  // 復(fù)制內(nèi)容,開辟新內(nèi)存
    }
    return *this;
}

問題在哪? 當(dāng)你在傳遞大對象時,特別是臨時對象,復(fù)制操作會帶來不必要的性能開銷。

2. 思考一個場景

MyString createGreeting() {
    MyString greeting("Hello, world!");
    return greeting;  // 返回時會創(chuàng)建臨時對象
}

void useString() {
    MyString s = createGreeting();  // 從臨時對象復(fù)制構(gòu)造
    // 臨時對象隨后被銷毀
}

在這個過程中,我們做了什么?

  • 創(chuàng)建greeting,分配內(nèi)存并填充"Hello, world!"
  • 返回時創(chuàng)建臨時對象,又分配內(nèi)存并復(fù)制"Hello, world!"
  • 構(gòu)造s時,再次分配內(nèi)存并復(fù)制"Hello, world!"
  • 臨時對象銷毀,釋放其內(nèi)存

三次內(nèi)存分配,兩次不必要的復(fù)制! 有沒有更好的方法?

3. 移動語義:合法的資源"竊取"

C++11引入的移動語義允許我們直接"偷取"即將被銷毀的對象的資源:

// 移動構(gòu)造函數(shù)
MyString(MyString&& other) noexcept {
    length = other.length;
    data = other.data;         // 直接偷走指針
    other.data = nullptr;      // 把被偷的對象標(biāo)記為"已被偷"
    other.length = 0;
}

// 移動賦值運算符
MyString& operator=(MyString&& other) noexcept {
    if (this != &other) {
        delete[] data;         // 釋放自身原有資源
        length = other.length;
        data = other.data;     // 偷走other的資源
        other.data = nullptr;  // 標(biāo)記other為"已被偷"狀態(tài)
        other.length = 0;
    }
    return *this;
}

現(xiàn)在我們的代碼變成:

MyString createGreeting() {
    MyString greeting("Hello, world!");
    return greeting;  // 返回時會創(chuàng)建臨時對象
}
void useString() {
    MyString s = createGreeting();  // 現(xiàn)在可以移動構(gòu)造,而不是復(fù)制
}

這里的巨大優(yōu)勢在于:

  • 只有一次內(nèi)存分配(在createGreeting里面)
  • 沒有不必要的復(fù)制
  • 通過簡單地轉(zhuǎn)移指針?biāo)袡?quán),我們獲得了巨大的性能提升

4. 移動語義背后的魔法細節(jié)

來聊聊那些你必須知道的移動語義細節(jié),我盡量用最簡單的語言和例子說明:

(1) 被移動對象必須保持有效但狀態(tài)不確定

MyString source("Hello");
MyString dest = std::move(source);  // 移動構(gòu)造

// 此時source仍然是有效的對象,但它的內(nèi)容是什么?
// 我們只知道它不再擁有原來的字符串資源,但具體狀態(tài)不確定
// 你可以對source賦新值,但不應(yīng)該使用它的當(dāng)前值

// 此時直接使用source是危險的
std::cout << source.data;    // 危險!source可能已經(jīng)是空指針

// 但給source賦新值是安全的
source = MyString("World");  
// 賦值后,使用source又變得安全了
std::cout << source.data;    // 現(xiàn)在安全了,因為source有了新值

為了安全,最好的做法是把被移動的對象當(dāng)作"已經(jīng)被掏空"的東西,不要再使用它的值,直到你給它賦予新值。

(2) 移動操作應(yīng)該標(biāo)記為noexcept(這很重要)

// 最佳實踐:標(biāo)記移動操作為noexcept
MyString(MyString&& other) noexcept {
    data = other.data;
    other.data = nullptr;
}

為什么要加noexcept?這涉及到STL容器的性能優(yōu)化:

// 這樣STL容器在擴容時會優(yōu)先使用移動而不是復(fù)制
std::vector<MyString> vec;
vec.push_back(MyString("test"));  // 當(dāng)vector需要擴容時會使用移動操作

關(guān)鍵點:雖然在簡單移動場景下,不加noexcept也會調(diào)用移動構(gòu)造函數(shù),但在STL容器的特定操作中(特別是擴容時),noexcept會產(chǎn)生重要影響:

  • 如果移動構(gòu)造標(biāo)記了noexcept,STL容器知道移動操作不會拋異常,就可以放心使用更高效的移動操作
  • 如果沒有標(biāo)記noexcept,某些STL實現(xiàn)會采取保守策略,在需要保證異常安全性的場景下退回到復(fù)制操作

簡而言之,加上noexcept是一種優(yōu)化提示,告訴STL容器:"放心,我的移動操作絕對不會拋異常,你可以放心使用它來提高性能!"

(3) 簡單類型的移動等同于復(fù)制

// 對于int、double這樣的簡單類型,移動和復(fù)制沒有區(qū)別
int a = 5;
int b = std::move(a);  // 和 int b = a; 效果完全一樣
std::cout << a;  // 輸出仍然是5,因為int的移動就是復(fù)制

移動語義只對管理資源(指針、句柄等)的類有明顯優(yōu)勢。

(4) 自定義類的規(guī)則變了

在C++11之前,如果你不定義任何特殊函數(shù),編譯器會自動生成:

  • 默認(rèn)構(gòu)造函數(shù)
  • 復(fù)制構(gòu)造函數(shù)
  • 復(fù)制賦值運算符
  • 析構(gòu)函數(shù)

C++11之后,又多了兩個:

  • 移動構(gòu)造函數(shù)
  • 移動賦值運算符

但有個重要規(guī)則:如果你自定義了復(fù)制操作,編譯器不會生成移動操作;反之亦然。

class OnlyCopy {
public:
    OnlyCopy(const OnlyCopy& other) { /*...*/ }  // 只定義了復(fù)制構(gòu)造
    // 編譯器不會生成移動構(gòu)造和移動賦值
};

class OnlyMove {
public:
    OnlyMove(OnlyMove&& other) noexcept { /*...*/ }  // 只定義了移動構(gòu)造
    // 編譯器不會生成復(fù)制構(gòu)造和復(fù)制賦值
};

如果你想要兩者都有,就必須兩者都自己定義!

5. 現(xiàn)實中移動語義的使用場景

來看幾個真實場景,理解移動語義為什么這么強大:

(1) 容器擴容的性能飛躍

當(dāng)std::vector需要擴容時,它需要把所有元素從舊內(nèi)存轉(zhuǎn)移到新內(nèi)存。看看有無移動語義的區(qū)別:

// 創(chuàng)建一個字符串向量并添加數(shù)據(jù)
std::vector<MyString> names;
for(int i = 0; i < 1000; ++i) {
    names.push_back(MyString("很長的字符串..."));  // 每次可能導(dǎo)致擴容
}

沒有移動語義時:

  • 分配新內(nèi)存(原來大小的1.5或2倍)
  • 復(fù)制構(gòu)造所有元素到新內(nèi)存(每個元素都要新分配內(nèi)存并復(fù)制字符串內(nèi)容)
  • 析構(gòu)舊內(nèi)存中的所有元素
  • 釋放舊內(nèi)存

有移動語義時:

  • 分配新內(nèi)存
  • 移動構(gòu)造所有元素到新內(nèi)存(只是轉(zhuǎn)移指針,不復(fù)制內(nèi)容)
  • 析構(gòu)舊內(nèi)存中的所有元素(這些都是被移動過的空殼)
  • 釋放舊內(nèi)存

性能對比:對于管理大量內(nèi)存的類(如字符串、容器),移動比復(fù)制可能快幾倍甚至數(shù)10倍!

(2) 返回大對象的函數(shù)

C++里返回大對象一直是個性能擔(dān)憂,看看移動語義怎么解決這個問題:

// 返回一個包含百萬個元素的向量
std::vector<int> createLargeVector() {
    std::vector<int> result;
    for(int i = 0; i < 1000000; ++i) {
        result.push_back(i);
    }
    return result;  // 返回一個巨大的對象!
}

// 使用這個函數(shù)
void useVector() {
    std::vector<int> myVec = createLargeVector();  // 不用擔(dān)心性能了!
}

C++98時代:這會導(dǎo)致一次額外的復(fù)制,程序員經(jīng)常被迫使用輸出參數(shù)或動態(tài)分配來避免這個開銷。

C++11移動語義后:編譯器會自動優(yōu)化,使用移動語義避免多余復(fù)制。甚至在更多情況下,編譯器可能應(yīng)用返回值優(yōu)化(RVO),完全消除復(fù)制/移動。

(3) 交換(swap)操作的巨大改進

移動語義讓交換操作變得超級高效:

// 交換兩個很大的字符串
MyString a("超長字符串...");
MyString b("另一個超長字符串...");

// C++98的交換
void old_swap(MyString& a, MyString& b) {
    MyString temp(a);  // 復(fù)制構(gòu)造
    a = b;            // 復(fù)制賦值
    b = temp;         // 復(fù)制賦值
}

// C++11的交換
void new_swap(MyString& a, MyString& b) {
    MyString temp(std::move(a));  // 移動構(gòu)造
    a = std::move(b);             // 移動賦值
    b = std::move(temp);          // 移動賦值
}

性能提升:在管理大量資源的類中,移動交換可能比傳統(tǒng)交換快幾十倍!這也是為什么C++11標(biāo)準(zhǔn)庫全面升級了std::swap的實現(xiàn)。

(4) 智能指針與移動語義的完美配合

移動語義讓std::unique_ptr真正變得易用。理解這一點很簡單:

// unique_ptr的核心特點:獨占所有權(quán),不允許復(fù)制
std::unique_ptr<BigObject> p1(new BigObject());
// std::unique_ptr<BigObject> p2 = p1;  // 錯誤!不能復(fù)制

// 但有了移動語義,我們可以轉(zhuǎn)移所有權(quán):
std::unique_ptr<BigObject> p2 = std::move(p1);  // 成功!p1變?yōu)榭眨琾2獲得所有權(quán)

這讓我們能輕松地在函數(shù)間傳遞unique_ptr:

std::unique_ptr<BigObject> createObject() {
    auto ptr = std::make_unique<BigObject>();
    // 配置對象...
    return ptr;  // 自動使用移動語義,資源所有權(quán)被轉(zhuǎn)移
}

void processObject() {
    auto obj = createObject();  // obj獲得所有權(quán)
    // 使用obj...
}  // obj自動釋放資源

簡單來說:沒有移動語義,unique_ptr 就像一個不能傳遞的"門票";有了移動語義,它變成可以轉(zhuǎn)讓但同一時刻只有一人持有的"門票"。這讓我們能同時擁有安全性和靈活性!

6. 小貼士:移動語義的失效情況

有些情況下即使你用了std::move,移動語義也會失效:

對象沒有移動操作 如果類沒有定義移動構(gòu)造/賦值,std::move會退化為復(fù)制操作。

移動操作被禁用 某些類可能顯式刪除了移動操作。

移動不如復(fù)制快 對于某些簡單類型,編譯器可能選擇復(fù)制而不是移動,因為復(fù)制可能更高效。

現(xiàn)在,你對"偷"資源的藝術(shù)是不是有更清晰的理解了?移動語義是C++11最重要的特性之一,掌握它會讓你的代碼性能有質(zhì)的飛躍!

四、std::move:不是移動,是變身大法

這個名字起得有點坑人,很多C++新手看到std::move就以為它會移動什么東西。事實上:

std::move根本不會移動任何東西!

1. std::move的真相

它的真正作用非常簡單:把一個左值強制轉(zhuǎn)換為右值引用類型。

// std::move簡化版實現(xiàn)(揭開它的神秘面紗)
template<typename T>
typename std::remove_reference<T>::type&& move(T&& param) {
    return static_cast<typename std::remove_reference<T>::type&&>(param);
}

不用被上面的代碼嚇到,它本質(zhì)上就是一個類型轉(zhuǎn)換函數(shù),相當(dāng)于:

// 偽代碼,更容易理解
template<typename T>
右值引用類型 move(參數(shù)) {
    return 把參數(shù)轉(zhuǎn)成右值引用類型;
}

2. 為什么叫"變身大法"?

想象一下,std::move就像是一個魔法標(biāo)簽:

MyString a("Hello");  // a是個普通的左值

// std::move在這里施了個魔法!
MyString b = std::move(a);  // 把a貼上"可以偷我"的標(biāo)簽

這個魔法做了什么?

  • 它把a從"普通左值"變身為"右值引用"
  • 這個變身讓編譯器調(diào)用移動構(gòu)造函數(shù)而不是復(fù)制構(gòu)造函數(shù)
  • 移動構(gòu)造函數(shù)看到右值引用,心想:"這家伙被標(biāo)記為可偷了,我可以偷它的資源!"

3. 看個實際例子

#include <iostream>
#include <string>
usingnamespacestd;

int main() {
    string name = "Hello World";  // name是個左值
    
    cout << "原始name: " << name << endl;
    
    // 錯誤理解:下面這行會移動name
    string new_name = std::move(name);
    
    // 真相:std::move只是轉(zhuǎn)換類型,真正的移動發(fā)生在string的移動構(gòu)造函數(shù)中
    cout << "移動后name: " << name << endl;  // name可能為空或未定義狀態(tài)
    cout << "new_name: " << new_name << endl;
}

輸出可能是:

原始name: Hello World
移動后name: 
new_name: Hello World

為什么說"可能為空"?因為被移動的對象處于"有效但未指定"的狀態(tài),標(biāo)準(zhǔn)只保證它可以安全析構(gòu),不保證它的具體內(nèi)容。實際上對于std::string,大多數(shù)實現(xiàn)中移動后的字符串會變?yōu)榭铡?/p>

4. std::move使用注意事項

  • 移動后不要再使用原對象的值:
vector<int> v1 = {1, 3, 5};
auto v2 = std::move(v1);  // v1被轉(zhuǎn)換成右值引用
// cout << v1[0] << endl;  // 危險!v1的狀態(tài)未定義
v1 = {1, 2, 3};  // 重新賦值后才能安全使用
  • 返回值不需要std::move:
// 不要這樣做
vector<int> createVector() {
    vector<int> result = {1, 2, 3};
    return std::move(result);  // 多余的!編譯器已經(jīng)會自動應(yīng)用返回值優(yōu)化
}

// 正確做法
vector<int> createVector() {
    vector<int> result = {1, 2, 3};
    return result;  // 編譯器會自動處理
}
  • 何時該用std::move
// 場景1:當(dāng)你確定不再需要某個變量時
string str = "hello";
doSomething(std::move(str));  // str的值被移走了

// 場景2:實現(xiàn)移動語義
void swap(T& a, T& b) {
    T temp = std::move(a);  // 移動a到temp
    a = std::move(b);       // 移動b到a
    b = std::move(temp);    // 移動temp到b
}

// 場景3:將對象插入容器
vector<MyObject> v;
MyObject obj;
v.push_back(std::move(obj));  // 避免不必要的復(fù)制

5. 小貼士:std::forward vs std::move

std::move和std::forward容易混淆:

  • std::move總是無條件地將參數(shù)轉(zhuǎn)為右值引用
  • std::forward根據(jù)模板參數(shù)類型有條件地轉(zhuǎn)換(完美轉(zhuǎn)發(fā),這個我們下篇聊)

6. 總結(jié):理解std::move

  • std::move不移動任何東西,它只是類型轉(zhuǎn)換
  • 真正的移動發(fā)生在移動構(gòu)造函數(shù)或移動賦值運算符中
  • 移動后,原對象仍然存在,但狀態(tài)不確定
  • 只有當(dāng)你不再需要原對象的值時,才使用std::move

記住這個比喻:std::move就像是給對象貼了個"可偷"的標(biāo)簽,告訴編譯器:"這個對象的資源可以被偷走!"

五、實戰(zhàn)例子:移動語義的威力

來看個實際例子,感受一下移動語義帶來的性能提升:

#include <iostream>
#include <vector>
#include <string>
#include <chrono>

int main() {
    constint NUM_STRINGS = 100000;  // 測試字符串?dāng)?shù)量
    constint STRING_SIZE = 1000;      // 每個字符串大小

    // 創(chuàng)建源數(shù)據(jù) - 兩個相同的字符串向量
    std::vector<std::string> sourceDataCopy;
    std::vector<std::string> sourceDataMove;

    // 填充源數(shù)據(jù)
    for (int i = 0; i < NUM_STRINGS; i++) {
        sourceDataCopy.push_back(std::string(STRING_SIZE, 'x'));
        sourceDataMove.push_back(std::string(STRING_SIZE, 'x'));
    }

    // 準(zhǔn)備目標(biāo)容器
    std::vector<std::string> destCopy;
    std::vector<std::string> destMove;
    destCopy.reserve(NUM_STRINGS);
    destMove.reserve(NUM_STRINGS);

    // 測試復(fù)制性能
    auto startCopy = std::chrono::high_resolution_clock::now();

    for (constauto& str : sourceDataCopy) {
        destCopy.push_back(str);  // 復(fù)制插入
    }

    auto endCopy = std::chrono::high_resolution_clock::now();

    // 測試移動性能
    auto startMove = std::chrono::high_resolution_clock::now();

    for (auto& str : sourceDataMove) {
        destMove.push_back(std::move(str));  // 移動插入
    }

    auto endMove = std::chrono::high_resolution_clock::now();

    // 計算時間
    auto copyTime = std::chrono::duration_cast<std::chrono::milliseconds>(endCopy - startCopy).count();
    auto moveTime = std::chrono::duration_cast<std::chrono::milliseconds>(endMove - startMove).count();

    // 輸出結(jié)果
    std::cout << "插入" << NUM_STRINGS << "個字符串(每個"
        << STRING_SIZE << "字符)的時間對比:\n";
    std::cout << "復(fù)制方式: " << copyTime << " ms\n";
    std::cout << "移動方式: " << moveTime << " ms\n";
    std::cout << "性能比率: 復(fù)制/移動 = "
        << (moveTime > 0 ? static_cast<double>(copyTime) / moveTime : 0)
        << " 倍\n";

    // 驗證移動確實發(fā)生了
    size_t emptyCount = 0;
    for (constauto& str : sourceDataMove) {
        if (str.empty()) emptyCount++;
    }
    std::cout << "被移動的源字符串?dāng)?shù)量: " << emptyCount << " (應(yīng)該接近于 " << NUM_STRINGS << ")\n";

    return0;
}

在我的電腦上運行結(jié)果(你的可能不同):

插入100000個字符串(每個1000字符)的時間對比:
復(fù)制方式: 170 ms
移動方式: 68 ms
性能比率: 復(fù)制/移動 = 2.5 倍
被移動的源字符串?dāng)?shù)量: 100000 (應(yīng)該接近于 100000)

看到差距了嗎?移動比復(fù)制快了近3倍!插入的字符串?dāng)?shù)量越多,效果越明顯!

六、何時使用移動語義?

理解了移動語義的原理后,關(guān)鍵問題來了:什么時候該用它?

下面我從實戰(zhàn)角度詳細講解各種常見場景。

1. 不再需要某對象的值時

當(dāng)你確定不再需要某個變量的值時,可以安全地"偷走"它的資源:

std::string name = "一個很長的字符串...";
std::string name2;

// 當(dāng)你確定之后不再使用name的值時
name2 = std::move(name);  // 直接偷走name的資源

// 此后不應(yīng)該再訪問name的值,除非重新賦值
// cout << name << endl;  // 危險!name可能已被掏空
name = "新值";  // 這樣是安全的

這是最常見也最實用的移動語義場景,尤其適用于大型對象(如字符串、容器等)的傳遞。

2. 函數(shù)返回值(通常不需要std::move)

對于函數(shù)返回值,編譯器通常會自動應(yīng)用返回值優(yōu)化(RVO)或移動語義:

std::vector<int> createVector() {
    std::vector<int> result;
    // 填充result...
    
    return result;  // 不需要std::move!
    // 編譯器會自動優(yōu)化,可能直接在調(diào)用者空間構(gòu)造,
    // 或者應(yīng)用移動語義
}

錯誤示范:

std::vector<int> createVector() {
    std::vector<int> result;
    // ...
    return std::move(result);  // 錯誤用法!可能阻礙RVO
}

為什么不需要std::move?因為C++標(biāo)準(zhǔn)允許編譯器在返回局部變量時省略復(fù)制/移動(RVO 返回值優(yōu)化),這比移動更高效。而使用std::move反而會阻止這種優(yōu)化!

3. 實現(xiàn)容器類或資源管理類

如果你在設(shè)計自己的容器或管理資源的類,移動語義是必不可少的:

class Buffer {
private:
    char* data;
    size_t size;

public:
    // 移動構(gòu)造函數(shù)
    Buffer(Buffer&& other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    
    // 移動賦值運算符
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
    
    // ... 其他成員 ...
};

4. 向容器中插入大型對象

當(dāng)向容器中添加元素時,移動可以避免不必要的復(fù)制:

std::vector<MyLargeObject> collection;

MyLargeObject obj = createLargeObject();  // 創(chuàng)建一個大對象

// 不好的方式:復(fù)制obj到容器
collection.push_back(obj);  // obj會被復(fù)制

// 更好的方式:移動obj到容器
collection.push_back(std::move(obj));  // obj被移動,避免復(fù)制
// 此后obj處于有效但未指定狀態(tài)

5. swap函數(shù)實現(xiàn)

移動語義讓交換操作變得更高效:

template <typename T>
void my_swap(T& a, T& b) {
    T temp = std::move(a);  // 移動a到temp
    a = std::move(b);       // 移動b到a
    b = std::move(temp);    // 移動temp到b
}

6. 函數(shù)參數(shù)使用右值引用

當(dāng)你想在函數(shù)內(nèi)部"竊取"參數(shù)資源時:

void processAndStore(std::string&& str) {
    // 因為參數(shù)是右值引用,我們知道調(diào)用者不再需要它
    storage.push_back(std::move(str));  // 可以安全地移動
}

// 調(diào)用方式
processAndStore(std::string("臨時字符串"));  // 直接傳遞臨時對象
std::string s = "hello";
processAndStore(std::move(s));  // 明確表示不再需要s的值

7. 什么時候不要使用移動語義?

了解不應(yīng)該使用移動的場景同樣重要:

(1) 當(dāng)你還需要使用源對象的值時

std::string name = "Alice";
std::string greeting = "Hello, " + std::move(name);  // 錯誤用法!
std::cout << "Name: " << name << std::endl;  // name的值現(xiàn)在不確定

(2) 不必要的std::move

// 不需要這樣做
return std::move(result);  // 多余的!編譯器會自動處理返回值優(yōu)化(RVO)

(3) 簡單類型(如int、double等)

int a = 5;
int b = std::move(a);  // 沒有效果,和 int b = a; 完全一樣

8. 移動語義自動觸發(fā)的地方

在某些情況下,移動語義會自動觸發(fā),無需顯式使用std::move:

(1) 返回局部變量

std::vector<int> createVector() {
    std::vector<int> result;
    // 填充result...
    return result;  // 編譯器通常會自動應(yīng)用移動語義或返回值優(yōu)化
}

(2) 臨時對象初始化

std::string s = std::string("hello") + " world";  // 右側(cè)臨時對象會被移動,而非復(fù)制

9. 移動語義測試

如何驗證移動語義是否真的生效?可以添加打印語句:

class MyClass {
public:
    MyClass() { std::cout << "構(gòu)造\n"; }
    MyClass(const MyClass&) { std::cout << "復(fù)制構(gòu)造\n"; }
    MyClass(MyClass&&) noexcept { std::cout << "移動構(gòu)造\n"; }
    // ...
};

int main() {
    MyClass a;
    MyClass b = a;           // 輸出"復(fù)制構(gòu)造"
    MyClass c = std::move(a); // 輸出"移動構(gòu)造"
}

10. 總結(jié):移動語義的最佳實踐

  • 當(dāng)確定不再需要原對象的值時,使用std::move
  • 為你的類實現(xiàn)移動操作,并標(biāo)記為noexcept
  • 函數(shù)參數(shù)中使用右值引用可以"竊取"臨時對象的資源
  • 移動后不要使用原對象的值,除非重新賦值
  • 對于大型對象,優(yōu)先考慮移動而非復(fù)制

記住:移動語義是C++性能優(yōu)化的重要武器!

七、小結(jié):左值、右值與移動語義的關(guān)系

  • 左值:有名字、有地址的東西
  • 右值:臨時的、即將消亡的東西
  • 左值引用&:綁定到左值的引用
  • 右值引用&&:綁定到右值的引用
  • 移動語義:利用右值引用從即將消亡的對象"偷"資源
  • std::move:把左值變身為右值引用,允許我們對左值應(yīng)用移動語義

八、寫在最后:移動語義小貼士

搞懂了移動語義的原理,我再給你幾個簡單實用的小貼士,幫你在實際編碼中用好這個特性:

1. 日常使用要點

記住移動后原對象就像"被偷了家":

string original = "Hello World";
string new_str = std::move(original);

// original現(xiàn)在可能是空的!除非你給它新值,否則別再用它

合適的場景才用std::move:

  • 當(dāng)你確定不再需要某個變量值時
  • 當(dāng)你要把資源從一個對象轉(zhuǎn)移到另一個對象時
  • 當(dāng)你往容器里插入大對象時

不要對簡單類型用std::move:

int a = 5;
int b = std::move(a);  // 沒意義,和 int b = a; 完全一樣

2. 自己寫類時的注意事項

實現(xiàn)移動操作時記得"掏空"原對象:

MyClass(MyClass&& other) noexcept {
    data = other.data;       // 偷資源
    other.data = nullptr;    // 記得標(biāo)記原對象"已被偷" 
}

移動操作最好標(biāo)記為noexcept:

  • 這樣能提高容器操作的性能
  • 使用noexcept關(guān)鍵字即可

如果實現(xiàn)了移動,通常也需要實現(xiàn)復(fù)制:

  • 大多數(shù)情況下兩者都需要
  • 在實現(xiàn)移動操作時,記得正確處理資源所有權(quán)

3. 簡單判斷口訣

  • 臨時對象或右值 → 自動觸發(fā)移動
  • 命名變量 → 需要std::move才能觸發(fā)移動
  • 移動后的變量 → 不要再使用它的值(除非重新賦值)

怎么樣,現(xiàn)在對左值、右值和移動語義是不是有了更清晰的理解?C++的這部分特性雖然開始有點繞,但掌握后真的能寫出更高效的代碼。

下次當(dāng)你看到std::move或者&&時,你就能自信地說:"我知道這是在做什么!"

責(zé)任編輯:趙寧寧 來源: 跟著小康學(xué)編程
相關(guān)推薦

2025-03-28 08:50:00

指針編程C 語言

2025-02-07 09:58:43

C++11Lvalue對象

2022-02-16 12:52:22

C++項目編譯器

2022-07-26 00:36:06

C#C++函數(shù)

2010-02-03 17:32:54

C++左值與右值

2020-08-11 11:00:16

左值引用右值引用移動語義

2012-02-13 10:18:42

C++ 11

2024-12-17 17:24:24

2025-06-03 10:10:00

C++左值右值

2025-06-06 07:35:06

C++表達式右值

2015-11-12 10:03:34

前端H5web

2024-03-05 09:55:00

C++右值引用開發(fā)

2017-01-13 23:06:45

swiftios

2021-11-10 12:13:02

HostonlyCookie瀏覽器

2024-01-31 23:51:22

C++移動語義代碼

2020-09-27 06:53:57

MavenCDNwrapper

2017-04-03 15:35:13

知識體系架構(gòu)

2009-11-12 09:37:14

Visual Stud

2021-12-03 17:22:09

CC++編程語言

2015-03-17 10:13:52

HTML5什么鬼
點贊
收藏

51CTO技術(shù)棧公眾號

国产又大又粗又长| 视频免费在线观看| 国产在线1区| 成人精品小蝌蚪| 国产91精品久久久| 久久久久久成人网| xvideos.蜜桃一区二区| 色播五月激情综合网| 欧洲美女和动交zoz0z| 污视频在线免费观看| 免费看欧美女人艹b| 久久久亚洲天堂| 亚洲免费看av| 菠萝蜜视频在线观看www入口| 国产区在线观看成人精品| 95av在线视频| 中文字幕在线日本| 亚洲毛片网站| 另类色图亚洲色图| 三上悠亚ssⅰn939无码播放 | 亚洲欧美自拍另类日韩| japanese色国产在线看视频| 国产精品理伦片| 精品国产综合久久| jizz中国少妇| 久久99热这里只有精品| 青青草成人在线| 久久99久久久| 天天色综合色| 中文字幕亚洲二区| 欧美丰满少妇人妻精品| 日韩中文字幕| 欧美一区二区三区婷婷月色| 久久久精品麻豆| 亚洲黄色免费av| 午夜天堂影视香蕉久久| av中文字幕av| 黄色的网站在线观看| 国产三级精品视频| 欧美精品七区| 先锋av资源站| av网站免费线看精品| 国产精选在线观看91| 精品国产一级片| 国产一区二区三区国产| 国产在线观看不卡| 中文字幕一区二区在线视频| 日韩精品欧美成人高清一区二区| 97视频免费看| 久久久午夜影院| 一区二区久久| 欧美在线www| 日韩在线 中文字幕| 久久精品一区二区国产| 青草成人免费视频| 最新中文字幕一区| 日韩电影一二三区| 国产精品久久久久久久久久免费| 精人妻无码一区二区三区| 视频一区中文字幕国产| 国产精品福利在线| 探花国产精品一区二区| 麻豆国产欧美一区二区三区| 国产精品中文字幕在线| 一区二区三区播放| 国产一区二区免费在线| 99c视频在线| 欧美 日韩 综合| 91一区一区三区| 欧美美乳视频网站在线观看| 黄色大片在线看| 国产精品不卡一区二区三区| 免费看污污视频| 国产啊啊啊视频在线观看| 午夜国产精品影院在线观看| 久久久亚洲精品无码| 三上悠亚激情av一区二区三区 | 国产视频一区二区视频| 国产成人免费精品| 欧美一二三区在线观看| 亚洲视频在线播放免费| 免费一区二区三区视频导航| 中文字幕日韩av| tube国产麻豆| 99成人精品| 国产精品入口尤物| 精品毛片在线观看| 91欧美一区二区| 亚洲欧洲日韩综合二区| 伊人电影在线观看| 色综合久久久久综合| 五月天婷婷亚洲| 久久365资源| 在线播放精品一区二区三区| 毛片aaaaa| 天堂成人免费av电影一区| 91久久精品国产91久久| 四虎影院在线播放| 国产女人18水真多18精品一级做| 免费cad大片在线观看| 在线天堂资源| 日韩一区二区视频| 91网站免费入口| 欧美一区二区三区另类| 热99久久精品| www.五月婷| 中文字幕不卡三区| 免费国产黄色网址| 国产电影一区| 亚洲欧美在线x视频| 青青草原在线免费观看| 日本人妖一区二区| 国产成人看片| 国产精品一区二区三区视频网站| 日韩欧美精品在线观看| 色诱av手机版| 久久久久久久久国产一区| 日韩美女av在线免费观看| 精品国产亚洲av麻豆| 国产精品美女久久久久久久| 97在线免费公开视频| 清纯唯美激情亚洲| 日韩在线激情视频| 国产精品久久久久久久久夜色| 成人午夜av影视| 51xx午夜影福利| 黑人一区二区三区| 国产性色av一区二区| 欧美不卡视频在线观看| 粉嫩久久99精品久久久久久夜| 一区二区三视频| 欧美日韩国产网站| 一本色道久久88综合日韩精品| 好吊妞视频一区二区三区| 懂色av一区二区在线播放| 成年丰满熟妇午夜免费视频| 亚洲无码精品一区二区三区| 一本大道色婷婷在线| 91精品国产欧美日韩| 美女网站视频色| 日本午夜精品视频在线观看 | 欧洲乱码伦视频免费| 91高清视频免费| 天天综合永久入口| 午夜精品久久久久久久99水蜜桃| 老熟女高潮一区二区三区| 亚洲成人一区| 444亚洲人体| 日本性爱视频在线观看| 日韩一区二区免费在线观看| 国产成人av免费在线观看| 韩国一区二区视频| 国产av第一区| 亚洲性视频在线| 欧美巨乳在线观看| 精品国产乱码一区二区三| 亚洲视频资源在线| 在线免费黄色小视频| 欧美日韩亚洲一区二区三区在线| 1区1区3区4区产品乱码芒果精品| 韩国av网站在线| 欧美成人在线直播| 日韩三级一区二区三区| 久久女同性恋中文字幕| 精品视频无码一区二区三区| 成人亚洲一区二区| 成人网在线视频| 黄色在线观看视频网站| 亚洲黄色www| 久久精品久久久久久久| 中文在线资源观看网站视频免费不卡 | 国产精品尤物福利片在线观看| 蜜芽在线免费观看| 亚洲精品一线二线三线| 国产精品第一页在线观看| 91日韩在线专区| 一区二区三区视频在线观看免费| 999久久久免费精品国产| 亚洲一区精品电影| 欧美xxxhd| 最近2019中文字幕在线高清| 精品久久久久久亚洲综合网站 | 91美女在线视频| 一区二区xxx| 国产精品s色| 欧美精品久久久| 日韩08精品| 国产91精品最新在线播放| 国产秀色在线www免费观看| 精品国产伦一区二区三区观看方式| 狠狠躁夜夜躁人人爽天天高潮| 久久精品日产第一区二区三区高清版 | 欧美精品99久久久| 久久久亚洲精品一区二区三区| 色婷婷一区二区三区av免费看| 亚洲成人直播| 亚洲在线色站| 日本国产精品| 99热99热| 欧美一级免费| 欧美亚洲在线播放| 日韩专区av| 中文字幕亚洲一区二区三区五十路 | 欧美交换国产一区内射| 国产亚洲午夜高清国产拍精品| 亚洲高清av一区二区三区| 亚洲欧美日韩国产一区| 加勒比海盗1在线观看免费国语版| 妖精视频一区二区三区免费观看| 亚洲自拍偷拍在线| 欧美日韩尤物久久| 91禁外国网站| 亚洲七七久久综合桃花剧情介绍| 一区二区亚洲欧洲国产日韩| 天天干天天草天天射| 制服丝袜亚洲播放| 天天天天天天天干| 精品久久久久久久久国产字幕| 丰满少妇被猛烈进入一区二区| 国产偷国产偷精品高清尤物| 中文字幕乱视频| 国产精品综合视频| 欧美成年人视频在线观看| 久久国产成人| 日韩在线一级片| 精品91久久久久| 男人添女人下部视频免费| 97精品在线| 一区二区三区四区在线视频| 成人久久电影| 特级西西444www大精品视频| 一本色道久久综合亚洲精品酒店| 国产精品乱码一区二区三区| 亚洲成人五区| 91精品国产一区二区三区动漫| 农村妇女一区二区| 国产精品久久视频| av在线不卡精品| 国产精品久久久av| 国产精品videossex撒尿| 国产精品久久久久久久av大片| 日韩精品专区| 国产精品嫩草影院久久久| 在线日本欧美| 国产精品高清在线| 福利一区二区| 国产日韩欧美中文| 成人动漫视频在线观看| 91日本在线观看| 国产色99精品9i| 91文字幕巨乱亚洲香蕉| 国产一区二区三区视频在线| av激情久久| 大陆精大陆国产国语精品| 国产伦精品一区二区三区高清 | 亚洲国产成人精品女人久久久 | 中文字幕日韩精品有码视频| 高清中文字幕一区二区三区| 在线观看91久久久久久| 日韩美女网站| 久久99亚洲精品| av电影院在线看| 国产91在线播放九色快色| 国产精品4hu.www| 成人国产精品久久久| 日韩精品一区二区三区中文| 国产精品久久久久av福利动漫| 看全色黄大色大片免费久久久| 免费看污久久久| 成人激情诱惑| 黄色网在线视频| 99在线|亚洲一区二区| 在线观看av日韩| 国产成人一级电影| 国产毛片毛片毛片毛片毛片毛片| 久久久国产精华| 日韩av手机在线免费观看| 亚洲国产日韩在线一区模特| 国产中文字幕视频| 91.com视频| 四虎在线视频免费观看| 在线视频中文亚洲| 日本小视频在线免费观看| 国产91精品在线播放| 国产视频网站一区二区三区| 精品毛片久久久久久| 欧美日韩在线网站| 91.com在线| 丝袜美腿亚洲一区| 中文字幕第六页| 久久精品一级爱片| 免费国产羞羞网站美图| 天天操天天色综合| 91tv国产成人福利| 日韩成人av网| a免费在线观看| 日韩美女视频中文字幕| 日本一区二区三区电影免费观看| 欧美日韩免费观看一区| 欧美日韩免费观看一区=区三区| 日韩欧美xxxx| 成人午夜在线免费| 五月天免费网站| 欧美午夜女人视频在线| 国产视频在线一区| 亚洲最新av在线网站| av电影免费在线看| 91夜夜揉人人捏人人添红杏| 免费av一区| 久久这里只有精品23| 国内欧美视频一区二区| 加勒比综合在线| 午夜婷婷国产麻豆精品| 国产高清精品软件丝瓜软件| 国产亚洲美女精品久久久| 黄毛片在线观看| 亚洲www视频| 日韩欧美1区| 亚洲精品高清无码视频| 成人a免费在线看| 欧美日韩亚洲国产另类| 欧美日韩五月天| 成年人在线看| 日产日韩在线亚洲欧美| 久久久久久毛片免费看| 欧美一二三不卡| 国产一区二区剧情av在线| 国产主播av在线| 欧美色视频在线观看| 成人在线观看网站| 国产成人精品综合| 久久99久久人婷婷精品综合 | 久久精品99国产国产精| 国产手机在线观看| 色综合久久中文字幕| 色播色播色播色播色播在线| 97精品国产97久久久久久| 波多野结衣一区二区三区免费视频| 椎名由奈jux491在线播放| 久热成人在线视频| 9.1片黄在线观看| 欧美日韩视频专区在线播放| av在线第一页| 成人黄色免费网站在线观看| 国产精品久久久久久久| theporn国产精品| 亚洲欧美日韩一区二区三区在线观看 | 美女脱光内衣内裤| 一本到一区二区三区| 精品电影在线| 国产精品三级在线| 久久美女视频| 色婷婷综合在线观看| 亚洲无线码一区二区三区| 秋霞欧美在线观看| 91精品国产91久久久| 免费看av成人| 午夜久久福利视频| 亚洲欧美日韩中文播放| 欧美一级免费片| 欧美一级视频一区二区| 北条麻妃国产九九九精品小说| 天天爽夜夜爽一区二区三区 | 久久精品国产亚洲av无码娇色| 亚洲第一精品电影| 亚洲欧洲日本韩国| 亚洲人体一区| 国产精品自拍av| 亚洲精品视频在线观看免费视频| 亚洲欧美制服中文字幕| 日本国产亚洲| 和岳每晚弄的高潮嗷嗷叫视频| 2023国产一二三区日本精品2022| 精品成人无码久久久久久| 久久天天躁狠狠躁老女人| 91欧美极品| 久草综合在线观看| 亚洲欧美色综合| 日韩在线无毛| 成人久久久久久久| 一区二区激情| 日本美女黄色一级片| 精品久久久久久久久久久久包黑料| 超碰在线公开| 亚洲日本精品| 成人短视频下载| 国产免费a视频| 欧美精品videofree1080p| 国产一区二区区别| 师生出轨h灌满了1v1| 色先锋资源久久综合| 99热国产在线| 日韩精品欧美一区二区三区| 国产高清无密码一区二区三区| 亚洲高清毛片一区二区| 久久夜色精品国产| 精品成人影院| 国产高清成人久久| 91麻豆精品国产91| 欧美日韩电影免费看|