C 右值引用

2021-08-21 21:11:01 字數 3548 閱讀 3737

class people 

string name_;

};people a("alice"); // 移動構造name

string bn = "bob";

people b(bn); // 拷貝構造name

構造a時,呼叫了一次字串的建構函式和一次字串的移動建構函式。如果使用const string& name接收引數,那麼會有一次建構函式和一次拷貝構造,以及一次non-trivial的析構。儘管看起來很蛋疼,儘管編譯器還有優化,但從語義來說按值傳入引數是最優的方式。如果你要在建構函式中接收std::shared_ptr並且存入類的成員(這是非常常見的),那麼按值傳入更是不二選擇。拷貝std::shared_ptr需要執行緒同步,相比之下移動std::shared_ptr是非常輕鬆愉快的。按值返回和接收輸入引數一樣,返回值按值返回也是最符合人類思維的方式。曾經有無數函式為了返回容器而不得不寫成這樣

void str_split(const

string& s, vector

* vec);

// 乙個按值語義定義的字串拆分函式。

//這裡不考慮分隔符,假定分隔符是固定的。

這樣要求vec在外部被事先構造,此時尚無從得知vec的大小。即使函式內部有辦法**vec的大小,因為函式並不負責構造vec,很可能仍需要resize。對這樣的函式巢狀呼叫更是痛苦的事情,誰用誰知道啊。有了移動語義,就可以寫成這樣

vector

str_split(const

string& s)

如果函式按值返回,return語句又直接返回了乙個棧上的左值物件(輸入引數除外)時,標準要求優先呼叫移動建構函式,如果不符再呼叫拷貝建構函式。儘管v是左值,仍然會優先採用移動語義,返回vector從此變得雲淡風輕。此外,無論移動或是拷貝,可能的情況下仍然適用編譯器優化,但語義不受影響。對於std::unique_ptr來說,這簡直就是福音。

unique_ptrcreate_obj(/*...*/)
當然還有更簡單的形式

unique_ptrcreate_obj(/*...*/)
在工廠類中,這樣的語義是非常常見的。返回unique_ptr能夠明確對所構造物件的所有權轉移,特別的,這樣的工廠類返回值可以被忽略而不會造成記憶體洩露。上面兩種形式分別返回棧上的左值和右值,但都適用移動語義(unique_ptr不支援拷貝)。

接收右值表示式沒有移動語義時,以表示式的值(例為函式呼叫)初始化物件或者給物件賦值是這樣的:

vector

str_split(const

string& s);

vector

v = str_split("1,2,3"); // 返回的vector用以拷貝構造物件v。為v申請堆記憶體,複製資料,然後析構臨時物件(釋放堆記憶體)。

vector

v2;

v2 = str_split("1,2,3"); // 返回的vector被複製給物件v(拷貝賦值操作符)。需要先清理v2中原有資料,將臨時物件中的資料複製給v2,然後析構臨時物件。

//注:v的拷貝構造呼叫有可能被優化掉,儘管如此在語義上仍然是有一次拷貝操作。同樣的**,在支援移動語義的世界裡就變得更美好了。vectorstr_split(const string& s);

vector

v = str_split("1,2,3"); // 返回的vector用以移動構造物件v。v直接取走臨時物件的堆上記憶體,無需新申請。之後臨時物件成為空殼,不再擁有任何資源,析構時也無需釋放堆記憶體。

vector

v2;

v2 = str_split("1,2,3"); // 返回的vector被移動給物件v(移動賦值操作符)。先釋放v2原有資料,然後直接從返回值中取走資料,然後返回值被析構。

//注:v的移動構造呼叫有可能被優化掉,儘管如此在語義上仍然是有一次移動操作。不用多說也知道上面的形式是多麼常用和自然。而且這裡完全沒有任何對右值引用的顯式使用,效能提公升卻默默的實現了。物件存入容器這個問題和前面的建構函式傳參是類似的。不同的是這裡是按兩種引用分別傳參。參見std::vector的push_back函式。void push_back( const t& value ); // (1)

void push_back( t&& value ); // (2)

//不用多說自然是左值呼叫1右值呼叫2。如果你要往容器內放入超大物件,那麼版本2自然是不2選擇。

vector

> vv;

vector

v = ;

v.push_back("789"); // 臨時構造的string型別右值被移動進容器v

vv.push_back(move(v)); // 顯式將v移動進vv

困擾多年的難言之隱是不是一洗了之了?

std::vector的增長又乙個隱蔽的優化。當vector的儲存容量需要增長時,通常會重新申請一塊記憶體,並把原來的內容乙個個複製過去並刪除。對,複製並刪除,改用移動就夠了。

對於像vector這樣的容器,如果頻繁插入造成儲存容量不可避免的增長時,移動語義可以帶來悄無聲息而且美好的優化.

std::unique_ptr放入容器曾經,由於vector增長時會複製物件,像std::unique_ptr這樣不可複製的物件是無法放入容器的。但實際上vector並不複製物件,而只是「移動」物件。所以隨著移動語義的引入,std::unique_ptr放入std::vector成為理所當然的事情。容器中儲存std::unique_ptr有太多好處。想必每個人都寫過這樣的**:

myobj::myobj() 

// ...

}myobj::~myobj()

// ...

}

繁瑣暫且不說,異常安全也是大問題。使用vector>,完全無需顯式析構,unqiue_ptr自會打理一切。完全不用寫析構函式的感覺,你造嗎?unique_ptr是非常輕量的封裝,儲存空間等價於裸指標,但安全性強了乙個世紀。實際中需要共享所有權的物件(指標)是比較少的,但需要轉移所有權是非常常見的情況。auto_ptr的失敗就在於其轉移所有權的繁瑣操作。unique_ptr配合移動語義即可輕鬆解決所有權傳遞的問題。注:如果真的需要共享所有權,那麼基於引用計數的shared_ptr是乙個好的選擇。shared_ptr同樣可以移動。由於不需要執行緒同步,移動shared_ptr比複製更輕量。std::thread的傳遞thread也是一種典型的不可複製的資源,但可以通過移動來傳遞所有權。同樣std::future std::promise std::packaged_task等等這一票多執行緒類都是不可複製的,也都可以用移動的方式傳遞。

完美**==除了移動語義,右值引用還解決了c++03中引用語法無法**右值的問題,實現了完美**,才使得std::function能有乙個優雅的實現。這部分不再展開了。

總結移動語義絕不是語法糖,而是帶來了c++的深刻革新。移動語義不僅僅是針對庫作者的,任何乙個程式設計師都有必要去了解它。儘管你可能不會去主動為自己的類實現移動語義,但卻時時刻刻都在享受移動語義帶來的受益。因此這絕不意味著這是乙個可有可無的東西。

c 左值 右值 右值引用 左值引用

c 裡一切值必須屬於左值 右值兩者之一。左值 一切變數 包括用const修飾的變數 物件 包括引用都屬於左值 右值 一切字面值 可以是巨集 臨時無名物件 函式返回值 表示式 如a n 說明一下 函式返回值,返回的是某乙個型別的值,並不是返回變數。左值並不是說能放在 左邊的值就是左值 雖然用const...

c 左值 右值 左值引用 右值引用

在c語言中,左值認為是賦值語句的左側,右值認為是賦值語句的右側。在c 中,意義稍有不同。c 中,每乙個表示式會產生乙個左值或者右值,相應的,該表示式也就被稱作 左值表示式 右值表示式 乙個左值表示式的求值結果是乙個物件或者是乙個函式。左值可以當右值使用,而右值不能當左值使用。c prime 中這麼簡...

C 左值 右值 左值引用 右值引用

就變數而言,對於一些變數,我們只會讀取並使用它們的值,而不會改變他們的值 唯讀 對於其餘的變數,我們既會讀取它們的值,有的時候還會改變它們的值 讀寫 這是很常見的。在c 中,前一種變數稱為右值,後一種變數稱為左值,例如 int a 1 a是左值,1是右值稍稍不同的一點是,在c 中,乙個變數是左值還是...