2015-07-21 101 views
56

x是以前已經初始化的某種類型的變量。是以下行:是'x = std :: move(x)`undefined?

x = std::move(x) 

undefined?標準在哪裏,它對此有何評論?

+3

它是(用戶)實現定義的,它具有與傳統賦值運算符相同的問題。 –

+4

它有什麼意義? – inf

+3

@πάνταῥεῖuuum,什麼?這是一個賦值,而不是初始化...顯然'x'需要首先聲明,並且不必*不需要初始化即可聲明。所以假設OP意味着將初始化對象移動到自身上,如果這觸發了UB,那麼不是缺少初始化。移動意味着對象「處於未指定但有效的狀態」(這是標準如何制定的)。 –

回答

51

沒有實現,這不是不確定的行爲,這將是實現定義的行爲,它將取決於如何實施移動分配。

與此相關的是LWG issue 2468: Self-move-assignment of library types,請注意這是一個活躍的問題,並且沒有官方提議,因此應該將其視爲指示性的而不是明確的,但它確實指出了涉及標準庫的部分並指出他們目前有衝突。它說:

假設我們寫

vector<string> v{"a", "b", "c", "d"}; 
v = move(v); 

應該是什麼v的狀態是什麼?該標準沒有說什麼 具體關於自我移動分配。標準的幾個 部分中有相關的文字,目前尚不清楚如何協調它們。

[...]

這不是從文本如何把這些碎片拼湊起來清楚,因爲目前還不清楚哪一個優先。也許17.6.4.9 [res.on.arguments]獲勝(它強加了MoveAssignable需求中未提及的隱式前置條件,因此v = move(v)未定義),或者可能是23.2.1 [container.requirements.general ](它明確地給出了Container :: operator =的額外保證,超出了一般庫函數的保證範圍,所以v = move(v)是沒有操作的),或者別的什麼。

在我檢查的現有實現上,爲了什麼是值得的,v = move(v)似乎清除了向量;它沒有保持矢量不變,也沒有導致崩潰。

,並提出:

非正式:改變MoveAssignable和集裝箱需求表(和提舉分配任何其他要求的表,如果有的話),使之明確得到x =移動(x)是定義的行爲,它使x處於有效但未指定的狀態。這可能不是標準今天所說的,但它可能是我們的意圖,它與我們告訴用戶以及實際執行的內容是一致的。

注意,對於內置類型這基本上是一個拷貝,我們可以從C++ 14標準牽伸部看到5.17[expr.ass]

在簡單賦值(= ),表達式的值會替換左邊的 操作數引用的對象的值。

這比上課的情況,其中5.17說不同:

如果左操作數是類類型,該類應完整。由複製/移動賦值運算符(12.8,13.5.3)定義 對類的對象的賦值。

請注意,clang有self move warning

+0

我不會在LWG 2468中提出這個建議,這只是問題提交者的建議,並不一定反映LWG的觀點。根據所寫的標準,我會爭辯說[res.on.arguments]中的限制爲標準庫類型提供了自動分配UB。 –

+0

@ T.C。這是公平的一點,我應該更加小心我的措辭。我更仔細地更新了措辭。 –

+2

「只是一個建議」是輕描淡寫的; Matt Austern是最受尊敬的LWG成員之一。我不是100%確定他是否仍然是LWG的主席,但他已經很長時間了。 – MSalters

14

它將調用X::operator = (X&&),所以它是由管理此情況下(因爲它是爲X::operator = (const X&)完成)

+0

如果我使用copy-swap成語,我定義了一個統一的賦值運算符(通過值而不是const ref捕獲),該怎麼辦?在這種情況下,我無法檢查運營商實施內部的自我分配。 – becko

9

所要做的就是致電X::operator=(X&&)(具有左值限定「*this」)。

在原始類型上,std::move幾乎沒有興趣,並且根本不與=進行交互。所以這隻適用於類類型的對象。

現在,對於std(或由其模板之一生成)中的類型,對象將傾向於保持未指定(但有效)的狀態。這不是未定義的行爲,但它不是有用的行爲。

必須檢查每個給定的X::operator=(X&&)的語義,檢查std中的每種類型對於堆棧溢出答案來說「太寬泛」。他們甚至可能自相矛盾。

一般來說,當從一個對象中傳遞給對象時,您正在向消費者傳達「您不關心對象在之後的狀態」。因此,使用x = std::move(x)是不禮貌的,因爲您(通常)在操作完成後(正如您指定的那樣)在意x處於什麼狀態。你在同一個操作中使用同一個對象作爲左值和右值,這並不是一種好習慣。

一個有趣的例外是默認std::swap,這是這樣的:

template<class T> 
void swap(T& lhs, T& rhs) { 
    T tmp = std::move(lhs); 
    lhs = std::move(rhs); 
    rhs = std::move(tmp); 
} 

中間線,lhs = std::move(rhs),做一個x = std::move(x)如果在同一對象上調用交換兩次。

但是,請注意,我們不在乎該行在完成之後處於什麼狀態x;我們已經在tmp中存儲了x的狀態,我們將在下一行中恢復它。

+1

圖書館認爲'std :: move(x)',或一般的rvalues,因爲「別人別名」。我發現了一個有用的觀點。 –

+3

*「使用'x = std :: move(x)''因此是不禮貌的」*然而它可能發生在通用的'swap'中,可能在一些算法中。 – dyp

+1

@dyp'template void swap(T&lhs,T&rhs){T tmp = std :: move(lhs); lhs = std :: move(rhs); rhs = std :: move(tmp); }'確實做了一個'x = std :: move(x);'它的確在一個上下文中這樣做*不管'*'後面的狀態是什麼*然而,因爲它的狀態在下一行被覆蓋和「保存」在上一行) – Yakk

相關問題