C 的move構造是否是設計失敗的?

時間 2021-05-30 20:15:03

1樓:jameswhale

在C++裡面,物件通過建構函式生成、通過析構函式銷毀,這個是最基本的原則,突破這個的話,C++也就不是「物件導向」程式設計了,也不大可能為了方便move在某些場景下的「方便」(其實應該是程式設計師自己需要注意,正如需要考慮記憶體的釋放一樣)而突破這個原則。

move這個特性的引入,最主要是為了解決「不必要的記憶體分配與釋放」,提高程式的執行效率,也減少了記憶體碎片化,例如,乙個臨時string物件,可以通過「竊取」其內部的字串記憶體給其他string物件,這樣可以減少一次記憶體分配(新的string物件中的記憶體)與記憶體釋放(臨時string物件中的記憶體)。這個是減少的string內的字串記憶體的分配與釋放,和string物件自身的生成與銷毀沒有關係。

另外,在C++語言這裡,一段記憶體的生命週期完全由程式設計師負責,所以在實現移動建構函式的時候需要注意,一段記憶體不能同時被兩個物件擁有,例如,物件中的成員變數指標p指向一段記憶體,被移動物件中的指標p要賦值為nullptr,否則會因為同一記憶體被釋放兩遍而coredump(物件析構函式要記得釋放這段記憶體,否則就出現記憶體洩露了)。

2樓:

我在想是不是可以考慮引入乙個新運算子「&&=」,表示移動賦値,賦値完後右邊物件自動析構,並且繼續使用就報錯。或者引入乙個std::move的語法糖「&&」,取右値。

但是最好std::forward也有乙個語法糖與之對應。

3樓:Euclid級收容物

關於後面的型別強轉…題主你知道move不知道forward嗎?你這是陷入了移動語義無法自拔,然而它和forward一樣都只是做了型別轉換而已。

不失敗。它解決了cpp的變數都是值語義的大背景下資源所有權轉移的問題。你也可以用指標來部分地解決這個問題,但是類似於unique_ptr*這種型別就很蠢不是嗎?

另外也無法解決部分子資源需要移動而部分不需要的問題。

如果你說的是他的語法,我也很不贊成,畢竟延續了cpp之前的一貫風格。

非要說有什麼黑點,可能就是沒有像rust一樣天然把移動語義作為預設的變數賦值語義

4樓:魚你太美

比如乙個vector,move完之後就成了乙個空vector,使用者依然可以後續對這個vector進行push_back等操作,也需要析構。

rust move之後就不能再使用這個值,也不會析構,相當於這個值完全被移走了,沒有了。C++由於歷史原因沒法保證move之後使用者不再使用,所以設計成move之後置空狀態,好像也是相容歷史的前提下比較正常的設計吧。

5樓:yy xx

我隨手定義了乙個YesRef

#include

template< typename T >class YesRef ;

template

YesRef(T &&)->YesRef< std::remove_reference_t< T > >;

template

void testYes(YesRef)

int main(int, charconst int yes;

testYes(YesRef);

}你問我這個YesRef有什麼用?

抱歉,我可以定義任何語義。

你覺得Move語義不行?

自己設計乙個你覺得行的語義不就行了。

C++正在變成乙個編譯器,想想都可怕。

6樓:「已登出」

移動語義可能是晦澀難懂的,這裡以乙個簡單的例子來說明:

class

string

}只有乙個指標,指向在堆中分配了記憶體的指標。

由於我們要自己管理這個記憶體,我們必須遵循三個規則,現在實現析構函式和複製的建構函式。

~string

()string

(const

string

&source

)//複製的建構函式

在這個複製的建構函式中,定義了複製字串物件的含義,引數const string&source繫結到string型別的所有表示式,我們就可以這樣來複製:

stringa(

x);stringb(

x+y);

stringc(

some_function_returning_a_string

());

只有第一行,深度複製是必要的。因為我們後續可能要檢查x的值,當這個值無論以什麼方式改變的時候,我們會非常的驚訝。請注意:

我們要複製的x,後續要檢查的x,如果x的值改變,這3個x是完全相同的物件,也就是通常說的lvalues

第二行和第三行的引數就不是lvalues,是rvalues,因為底層字串物件沒有名稱,因此客戶端無法在後續再次檢查他們。rvalues表示在下乙個分號處被銷毀的臨時物件。在b和c初始化的時候,我們可以對源字串做任何事情,但是編譯器沒有辦法分辨應該採用哪個值,編譯器是沒有辦法分辨的。

也就是說,執行了這一句,當還沒有初始化,我們就改變了這個值,那麼編譯器是無法分辨的,不會使用我們原本希望的值。

c++ 0x中引入了乙個新的機制,叫做rvalues引用,它允許我們通過函式過載檢測rvalue引數,我們要做的就是編寫乙個帶有rvalue引用引數的建構函式。在建構函式的內部,只要我們將它保留在某個狀態,我們就可以對源字串執行任何操作。

string

(string

&&source

)// string&&是乙個字串的 rvalue 引用

在這個建構函式中,我們複製了指標,然後將原始的指標設定為null(以防止源物件的析構函式中的delete釋放我們剛剛複製的資料。而不是深度複製堆資料。實際上,我們竊取了最初屬於源字串的資料。

同樣,關鍵點是編譯器無法檢測到源字串已經更改了。由於我們沒有真正的複製,我們將這個建構函式稱為move建構函式。他的工作是將資源從乙個物件移動到另乙個物件而不是複製。

你應該已經理解了移動語義的基本定義。如果要實現複製運算子,應該是這樣的:

string& operator=(string sourcestd::swap(data, source.datareturn *this;

}這裡沒有rvalue引用,因為不需要它。

a=b複製建構函式將初始化source,因為b是lvalue。

a=x+y,移動建構函式將初始化source,因為x+y是rvalue。

不涉及深度複製,僅僅是簡單的有效的移動。

從這個例子中,應該能看到重點,rvalue引用和移動語義還有很多,這裡為了看起來很簡單,都省略了,如果想繼續了解的話,自己查一下資料吧。

7樓:「已登出」

是,C++不應該允許任何non trivial的move,或者說,C++所有的物件都應該是可以被直接move的,就像rust一樣,同時,move也不應該觸發析構。

不然程式設計師的自由度太高了。

8樓:Cyandev

右值引用和移動語義是兩件事情。C++ 設計右值引用的初衷應該不是為了實現完整的所有權機制,因為那個涉及到的東西太多了,變動也會比較大。

移動語義只不過是有了右值引用以後,C++ 提供的乙個優化手段,現有的語法很難使被「移動」的 xvalue 標記為已移動並且阻止後期的使用。這與 Rust 不同,畢竟 Rust 預設就是移動語義。

C++ 的文件也特別指出,移動語義的實現依靠開發者,也就是你的移動建構函式過載需要正確實現(拷貝所需資料 + 廢棄原始物件),並且開發者也要自己正確處理被「移動」後的物件,也就是說不能亂用舊物件了。

所謂的移動其實也就是更輕量版的建構函式罷了,並不是真正意義上的記憶體移動。

至於 std::move,命名確實不是很好,對初學者來說有很強的誤導性。

9樓:發發啊喂

我覺得這個是c++最成功的設計了!顯示的移動乙個變數!換別的語言,會給你無數的思考,比如這個返回值裡面拷貝了嗎?而用了這個,立刻知道拷貝個鬼!!

10樓:暮無井見鈴

我覺得不是失敗的,雖然它的設計目的可能不夠好。

如果讓移動操作把物件本身消耗掉,則物件的生命期、儲存期會呈現比以往更加複雜的結構。目前 C++ 的移動操作設計成這樣,可能是為了令物件模型盡可能少受影響。至於這對 C++ 本身好不好我很難確定。

這麼做的代價是引入「可平凡重定位(trivially relocatable)」的語義要求更困難了,而且不能凸顯 Moveable 比 Copyable 更基礎的設定。

11樓:

直接回答問題:是的,相當失敗,不過也算是符合C++一貫的畫風。

為什麼和很多回答者似乎在教育題主move應該怎麼用?也許是問題沒有提好,應該改成「C++用接受右值引用的建構函式/賦值運算子來表達所有權轉移的語義,這設計是否是失敗的」。

這樣就沒什麼歧義了。毫無疑問,rust在這個問題上的設計是更合理的。

12樓:大鈾子

然而,C++語言上,被移動的物件tmp,依然可以被使用,會呼叫析構函式。是否語言層面上,結束tmp的定義域,並且不再呼叫tmp的析構函式呢?

每隔一段時間,就有人把C++標準庫拿出來煉一煉。這個問題,在我的教科書上有寫,移動構造時,被移動的物件應該保留乙個「空」的物件,比如string應該留下空字串,而vector應該留下乙個0長度的陣列。這樣的設計其實很巧妙。

比如:#include

using

namespace

std;

intmain

()up

=move

(down

);//把下一層的待搜尋陣列移過來

}return0;

}(注:廣搜有很多種寫法,比如佇列、指標、下標等,這裡只是一種比較笨的方法。)

13樓:Tanki Zhang

rvalue reference (如int&&)引數(比如 move constructor)基本都是用來表達 ownership 的轉移,unique_ptr 就是乙個很好的例子。

更 general 的從語言層面來說,rvalue 這一整套的內容只是整個 value category 中的一員,結合 move semantics 用來幫助實現 value semantics 而已。

14樓:旅行者

最失敗的就是move這個名字起得不好

這個函式明明就是什麼都不做,轉換一下型別而已,這名字卻搞得都覺得這個函式做了move這個動作一樣。真正的move是要自己實現的。

C 的函式返回值是如何構造的?

你如果是想問為什麼少輸出乙個new的話,那是因為F在隱式建立返回值物件的過程中,呼叫的是複製建構函式A const A 而不是預設的無參建構函式A 所以你自定義的無參建構函式A 並沒有捕獲到這次構造。include using namespace std struct A A A const A o...

《明日方舟》中皇帝的利刃是否設計失敗?

這麼說吧,我是低練代肝,單核帶一隊精一也能打,如果輸出型就打一波流,非輸出型就打年糕,而且這個boss的血就連龜龜都能打掉,高輸出的幹員只要擺對了國度可以騙到角落,我感覺這個設計沒問題。至於最後五個人可以在boss轉圈的時候就把防禦陣型擺好,他的遠端傷害又不疼,等走過去就把人擺好不就行了嗎。 m18...

人類的演化是否是失敗的?

精神病老大哥 我以為,您認為的失敗之處恰恰是人類演化的成功之處。因為在目前看來,人類很可能是地球上唯一走上腦力特化這一演化道路的物種。正因為人類的肉體能力不夠強,所以與其演化出獵豹的速度去追羚羊,還不如動動腦子做一根能扔出去的長矛,在羚羊逃跑前殺死它。更何況演化,一沒方向,二沒計畫,你也沒法規劃它的...