[英]What lasts after using std::move c++11
在一個變量中使用std :: move之后,該變量可能是類中的一個字段,如:
class A {
public:
vector<string>&& stealVector() {
return std::move(myVector);
}
void recreateMyVector() {
}
private:
vector<string> myVector;
};
我如何重新創建矢量,就像一個清晰的矢量? 在std :: move之后myVector中還剩下什么?
常見的咒語是已經“移動”的變量處於有效但未指定的狀態。 這意味着可以銷毀並分配給變量,但沒有別的。
( Stepanov稱之為“部分形成”,我相信,這是一個很好的術語。)
要明確的是,這不是一個嚴格的規則; 相反,它是關於如何考慮移動的指導原則 :在你離開之后,你不應該再想要使用原始對象了。 任何嘗試與原始對象做一些非平凡的事情(除了分配或破壞它)都應該仔細考慮和證明。
但是,在每種特定情況下,可能存在對移動對象有意義的其他操作,並且您可能希望利用這些操作。 例如:
標准庫容器描述了它們的操作的前提條件; 沒有先決條件的操作都沒問題。 想到的唯一有用的是clear()
,也許是swap()
(但更喜歡賦值而不是交換)。 還有其他沒有先決條件的操作,例如size()
,但是按照上面的推理,你不應該在你剛才說不再需要的對象大小之后進行任何業務查詢。
unique_ptr<T, D>
保證在被移動之后,它為null,您可以在有條件地擁有所有權的情況下利用它:
std::unique_ptr<T> resource(new T); std::vector<std::function<int(std::unique_ptr<T> &)> handlers = /* ... */; for (auto const & f : handlers) { int result = f(resource); if (!resource) { return result; } }
處理程序如下所示:
int foo_handler(std::unique_ptr<T> & p) { if (some_condition)) { another_container.remember(std::move(p)); return another_container.state(); } return 0; }
通常可能讓處理程序返回一些其他類型的狀態,指示它是否從唯一指針獲取所有權,但由於標准實際上保證從一個唯一指針移動將其保留為null,我們可以利用它在唯一指針本身傳輸該信息。
將成員向量移動到本地向量,清除成員,按值返回本地。
std::vector<string> stealVector() {
auto ret = std::move(myVector);
myVector.clear();
return ret;
}
在std :: move之后myVector中還剩下什么?
std::move
不動,它只是一個演員。 它可能發生myVector
是調用后保持完整stealVector()
; 在下面的示例代碼中查看第一個a.show()
的輸出。 (是的,這是一個愚蠢但有效的代碼。)
如果膽量myVector
真的被盜(見b = a.stealVector();
在示例代碼),這將是在一個有效的但非指定狀態。 然而,它必須是可分配和可破壞的; 在std::vector
情況下,您也可以安全地調用clear()
和swap()
。 你真的不應該做任何關於向量狀態的其他假設。
我如何重新創建矢量,就像一個清晰的矢量?
一種選擇是簡單地在其上調用clear()
。 然后你肯定知道它的狀態。
示例代碼:
#include <initializer_list>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class A {
public:
A(initializer_list<string> il) : myVector(il) { }
void show() {
if (myVector.empty())
cout << "(empty)";
for (const string& s : myVector)
cout << s << " ";
cout << endl;
}
vector<string>&& stealVector() {
return std::move(myVector);
}
private:
vector<string> myVector;
};
int main() {
A a({"a", "b", "c"});
a.stealVector();
a.show();
vector<string> b{"1", "2", "3"};
b = a.stealVector();
a.show();
}
這將在我的機器上打印以下內容:
ABC
(空)
由於我覺得Stepanov在答案中被誤傳,所以讓我快速概述一下我自己:
對於std
類型(並且僅限於那些),標准指定移動的對象保留在着名的“有效但未指定”狀態。 特別是,沒有一種std
類型使用Stepanov的部分形成狀態,其中包括我認為的一些錯誤。
對於您自己的類型,您應該爭取默認構造函數以及移動的源對象以建立部分形成狀態,Stepanov在編程元素(2009)中將其定義為僅有效操作的狀態銷毀和分配新價值。 特別是,部分形成狀態不需要表示對象的有效值,也不需要遵循正常的類不變量。
與流行的看法相反,這並不是什么新鮮事。 自C / C ++誕生以來,部分形成的國家存在:
int i; // i is Partially-Formed: only going out of scope and
// assignment are allowed, and compilers understand this!
對於用戶來說,這實際上意味着永遠不要假設你可以使用移動對象做更多事情而不是銷毀它或為其分配新值,當然,除非文檔說明你可以做得更多,這通常是可能的容器通常可以自然而有效地建立空狀態。
對於班級作者來說,這意味着你有兩個選擇:
首先,你像STL那樣避免部分形成狀態。 但對於具有Remote State的類,例如pimpl'ed類,這意味着要表示有效值,要么接受nullptr
作為pImpl
的有效值,要么提示您在公共API級別定義nullptr
pImpl
意思是,包括 檢查所有成員函數中的nullptr
。
或者你需要為移動的(和默認構造的)對象分配一個新的pImpl
,這當然不是任何具有性能意識的C ++程序員所能做的。 然而,一個注重性能的C ++程序員也不願意使用nullptr
檢查來丟棄他的代碼,只是為了支持一個非常簡單的使用移動對象的小用例。
這帶來了第二種選擇:擁抱部分形成的國家。 這意味着,您接受nullptr
pImpl
,但僅適用於默認構造和移動對象。 nullptr
pImpl
表示部分形成狀態,其中僅允許銷毀和分配另一個值。 這意味着只有dtor和賦值運算符需要能夠處理nullptr
pImpl
,而所有其他成員都可以采用有效的pImpl
。 這有另一個好處:默認的ctor和移動運算符都可以是noexcept
,這對於在std::vector
使用很重要(因此在重新分配時使用移動而不是副本)。
示例Pen
類:
class Pen {
struct Private;
Private *pImpl = nullptr;
public:
Pen() noexcept = default;
Pen(Pen &&other) noexcept : pImpl{std::exchange(other.pImpl, {})} {}
Pen(const Pen &other) : pImpl{new Private{*other.pImpl}} {} // assumes valid `other`
Pen &operator=(Pen &&other) noexcept {
Pen(std::move(other)).swap(*this);
return *this;
}
Pen &operator=(const Pen &other) {
Pen(other).swap(*this);
return *this;
}
void swap(Pen &other) noexcept {
using std::swap;
swap(pImpl, other.pImpl);
}
int width() const { return pImpl->width; }
// ...
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.