資料結構與演算法讀書筆記 007 右值引用

2021-10-18 04:18:07 字數 3226 閱讀 8971

右值引用,是延長了物件的生命週期。右值引用是乙個持久的變數,它所引用的物件不會在本該被銷毀的時候銷毀。所以,右值引用,其實是乙個左值。

「右值引用」和「右值屬性」不同。右值屬性是「具有』即將被銷毀的』屬性的物件」,而右值引用是乙個左值,乙個變數。

也許可以(我猜)可以這樣理解:變數未嘗不可以理解為乙個指標。真正的資料,是記憶體中的那部分記憶體塊。而變數則指明了記憶體的1)最一開始的資料的位址,以及2)記憶體的大小(即資料型別)。開始位址➕大小,即可以指明一塊確定的區域。

我感覺std::move()這種函式還是太頂層了。其中用了remove_reference和static_cast,如果不深入更底層還是不明其到底幹了什麼。不過,推測將乙個左值move()後成為乙個右值,應該是發生了如下轉變:由於變數其實還是指標性質,只不過是由這個名字指向了一塊記憶體,move()則是將這塊記憶體變為,既由這個變數名稱指向,又由右值引用指向。

(變數在概念上其實等同於變數名稱,並不真正代表那塊資源)

所以說右值引用竊取資源,可能也沒錯。

c++會將臨時變數銷毀。比如乙個函式返回值在函式大括號闊回處的臨時變數。如果此時將此變數賦給乙個右值引用 - 即,這個函式的返回值是右值引用。猜想此時發生的事情是:將這塊記憶體繫結給了乙個右值引用。右值引用是乙個持續的變數,所以即使離開了函式的作用域,或不再需要臨時變數,也不會將這塊記憶體銷毀。

那麼,這個臨時變數不是還和這塊記憶體繫結這麼。在銷毀臨時變數時候會不會把這塊記憶體也返還呢?

猜想是不會。我猜是,如果把臨時物件繫結給右值引用,臨時物件消失,記憶體繫結給右值引用,不會再存在銷毀臨時物件的過程。

但是如果把左值std::move()給右值,左值不會消失,還可以繼續使用。而且更改左值的值,右值引用繫結的值也會跟著更改。我想部分原因是因為,畢竟c++標準規定,移後源是需要確保可析構的。既然c++這樣規定了,那內建型別,雖然沒有析構函式,但只要也要保證移後可以delete吧。。。(不過要注意,c++的標準大概率是給類型別的定義者說的,而且是要求「源物件不再指向被移動的資源」)試了一下:

1)移後源物件(原左值)的位址和右值引用相同

2)對移後源物件賦新值,右值引用也改變成此新值

3)可以對移後源物件delete。(我試了用delete指標)

4)即使左值繫結了右值引用,將其delete後,記憶體被返還,而且右值引用仍指向這塊記憶體。

5)重新對這塊記憶體分配物件,移後源和右值引用仍然指向這塊記憶體,而且使用他們的話,會得到新分配物件的值。

由上,我猜的是右值引用的直接繫結和std::move()對原來的資料的處理是不同的。繫結臨時變數直接廢掉臨時變數這個變數名,由右值引用接管資源。std::move()則由原來的變數名稱和右值引用共同管理這塊資源。

所以,右值引用是一種型別,(std::move()的本質是static_cast,而static_cast是改變型別),且是一種引用。如果用std::move()左值將此左值繫結在右值引用上,其實原理上和左值引用並無太大差別。

那麼,此時的右值引用,更像是一種約定。也就是所謂的c++語義,std::move()的語義。即不再使用移後源物件。有上面的發現總結處的注意事項:

由2)最好不要對移後源賦值,會影響右值引用的值

由3)4)5)最好不要對移後源物件delete,否則右值引用也會變為不確定的狀態

由3)4)5)由於可以delete的情況只是移後源物件是有指標指著的,所以這類指標最好在移後=nullptr

這種約定,也會體現在,或者說也許就是為了,規範類型別的定義者,或者右值引用的使用者的行為。這種約定體現在定義移動構造和移動賦值等比較關鍵的行為上。**移動的本質並未移動物體,而是用同一塊記憶體,去做不同的事情。這種做法導致了這種約定,即這塊記憶體之前的用法,不再使用,而僅僅使用其之後的用法。**此處,右值引用在「之後的用法」中,和乙個正常的變數並無區別。其作用就是在用法的轉換過程中,起到乙個把關和顯示提醒這個約定的作用。而其對寫程式的影響,則在於,它需要在移動構造和賦值中,作為形參型別和過載函式的形式進行把關,有了右值引用的函式,則會提醒人們遵守這個約定,並為了安全,影響**的寫法,如給之前的用法賦值nullptr。

對類型別的定義也是這樣,根據約定,移後源物件就不再使用了,因為如果把乙個類型別左值物件傳入移動賦值或建構函式,需要使用std::move()。而一旦使用了std::move(),約定上即不再使用移後源了。

當然,右值引用另外作用即延長記憶體塊資料的宣告週期。其實這時其命名含義的由來 - 去繫結乙個右值,乙個快要消失,不會再用它來做什麼的值。而std::move()左值到右值引用,則是引申了這個概念。把左值,雖然存在,但也當作再也不使用的值。從而引申到了語義層面,而非僅僅是物理的生命週期層面。

注意:函式的返回型別可以是右值,但1)不能在return後面寫上return 右值引用,這樣相當於將右值引用繫結在用值引用上,而右值引用是左值。右值引用不能繫結在右值應用上。2)不能直接返回乙個函式內的區域性變數,這樣也是在函式返回時候將乙個右值引用繫結在左值上。

右值引用和左值引用一樣,要在宣告的時候初始化。

右值引用可以賦予其它值。那右值引用指向的之前的值呢?呃,估計被覆蓋了吧,我猜。

上面的1)- 5)是我用xcode試出來的。而primer上亦有一句話

「如果我們忘記了改變s,first_free,則銷毀移後源物件就會釋放掉我們剛剛移動的記憶體」(p473)

亦是說明移後源和右值引用其實指向了同一塊記憶體。。。

另外,移動建構函式的自賦值檢測,亦是檢測this和移後源的位址是否相同(p474)

「在移動操作後,移後源物件必須保持有效的,可析構的狀態,但是使用者不能對其值進行任何假設」(p475)

這應該就是讓太多物件接管一塊資源對麻煩之處。這種雙重的約定,除了因為要廢掉移後源,還是一種雙重保險。而且為了不影響右值引用以及用到了右值引用的物件,還要將移後源賦值為空nullptr。

當然我也知道編譯器會做很多優化,實際的執行不一定是按照這樣來的。只是需要這樣理解。是比較頂層的理解。。。

移動操作不丟擲異常,原因:

1)優化編譯過程。預設是可能丟擲異常,如果可能丟擲異常,編譯器就有可能做一些額外的工作

2)某些標準庫函式需要知道移動操作明確不丟擲異常才會使用移動操作。解釋是用vector來舉例。(比如vector或自己定義的strvec的reallocate操作,重新分配2倍記憶體然後將元素拷貝進去。如果是拷貝建構函式發生異常,雖然重新分配的記憶體沒法拷貝完整,但原來的資料還在。但如果是移動每個元素時發生了異常,移後源按約定是不該再使用的,就導致新的和舊的vector或strvec各分了一部分可用的資料。)

c++ primer(第五版)isbn 978-7-121-15535-2

資料結構,演算法與應用:c++語言描述(第二版)isbn 978-7-111-49600-7

資料結構與演算法讀書筆記 簡介

資料 人們利用文字元號 數字符號以及其他規定的符號對現實世界的事物及其活動所做的抽象描述。學號 姓名 性別 資料元素 表示乙個事物的一組資料。是資料 集合 中的乙個 個體 是 資料的基本單位。學號和姓名和性別的整體的一組資料 資料項 構成資料元素的資料,是資料結構中討論的最小單位。學號和姓名和性別的...

演算法筆記pdf 資料結構演算法與os 讀書筆記整理

1 資料結構與演算法 1.1 資料結構與演算法 鄧俊輝 dsacpp zhousoft tsinghuadatastructoj alg4 kevin wayne algs4 演算法 第4版 筆記 王爭wangzheng0822 algo 1.2 leetcode 左程雲 python resolv...

資料結構讀書筆記

首先有個關於指標的問題 如果函式傳進來乙個指標 p,函式裡操作p不會影響原來的值,而如果有 或者 才會改變 比如 malloc的時候傳入指標的指標才行 o 1 刪除節點時末尾必須要便利一遍 刪除頭指標要變為刪除第二個節點。1.o 1 刪除指定位置的節點 如果為最後乙個節點還是必須遍歷一遍 2.倒數第...