3 回答

TA貢獻1789條經驗 獲得超8個贊
首先,讓我們看一下std::forward根據標準執行的操作:
§20.2.3 [forward] p2
返回值: static_cast<T&&>(t)
(這T是顯式指定的模板參數,t它是傳遞的參數。)
現在,請記住參考折疊規則:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
(從這個答案中偷偷偷走了。)
然后,讓我們看一下一個想要使用完美轉發的類:
template<class T>
struct some_struct{
T _v;
template<class U>
some_struct(U&& v)
: _v(static_cast<U&&>(v)) {} // perfect forwarding here
// std::forward is just syntactic sugar for this
};
現在是一個示例調用:
int main(){
some_struct<int> s1(5);
// in ctor: '5' is rvalue (int&&), so 'U' is deduced as 'int', giving 'int&&'
// ctor after deduction: 'some_struct(int&& v)' ('U' == 'int')
// with rvalue reference 'v' bound to rvalue '5'
// now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int&&>(v)'
// this just turns 'v' back into an rvalue
// (named rvalue references, 'v' in this case, are lvalues)
// huzzah, we forwarded an rvalue to the constructor of '_v'!
// attention, real magic happens here
int i = 5;
some_struct<int> s2(i);
// in ctor: 'i' is an lvalue ('int&'), so 'U' is deduced as 'int&', giving 'int& &&'
// applying the reference collapsing rules yields 'int&' (& + && -> &)
// ctor after deduction and collapsing: 'some_struct(int& v)' ('U' == 'int&')
// with lvalue reference 'v' bound to lvalue 'i'
// now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int& &&>(v)'
// after collapsing rules: 'static_cast<int&>(v)'
// this is a no-op, 'v' is already 'int&'
// huzzah, we forwarded an lvalue to the constructor of '_v'!
}
我希望這個循序漸進的答案可以幫助您和其他人了解其std::forward工作原理。

TA貢獻1803條經驗 獲得超3個贊
我認為std::forwardas 的解釋static_cast<T&&>令人困惑。我們對轉換的直覺是將類型轉換為其他類型-在這種情況下,它將是對右值引用的轉換。不是!因此,我們正在使用另一種神秘事物來解釋一種神秘事物。這種特殊的演員表由Xeo的答案中的表格定義。但是問題是:為什么?所以這是我的理解:
假設我想向您傳遞一個std::vector<T> v您應該作為數據成員存儲在數據結構中的數據_v。天真的(和安全的)解決方案是始終將向量復制到其最終目的地。因此,如果通過中間函數(方法)執行此操作,則應將該函數聲明為引用。(如果將其聲明為按值獲取向量,則將執行其他完全不必要的復制。)
void set(const std::vector<T> & v) { _v = v; }
如果您手里有一個左值,這一切都很好,但是右值呢?假設向量是調用函數的結果makeAndFillVector()。如果您執行直接分配:
_v = makeAndFillVector();
編譯器將移動向量而不是復制向量。但是,如果您引入中介,set()則有關您的論據右值性質的信息將丟失,并將進行復制。
set(makeAndFillVector()); // set will still make a copy
為了避免這種復制,您需要“完美轉發”,這將每次都能獲得最佳代碼。如果給定左值,則希望函數將其視為左值并進行復制。如果給定了右值,則希望函數將其視為右值并將其移動。
通常,您可以通過set()分別為lvalues和rvalues 重載函數來做到這一點:
set(const std::vector<T> & lv) { _v = v; }
set(std::vector<T> && rv) { _v = std::move(rv); }
但是現在想象一下,您正在編寫一個接受T和調用set()該函數的模板函數T(不必擔心我們set()僅為向量定義的事實)。訣竅在于,set()當模板函數使用左值實例化時,您希望此模板調用第一個版本,而當使用右值初始化時,則希望該模板調用第二個版本。
首先,該函數的簽名應該是什么?答案是這樣的:
template<class T>
void perfectSet(T && t);
根據調用此模板函數的方式,T將在某種程度上以不同的方式神奇地推斷出類型。如果使用左值調用它:
std::vector<T> v;
perfectSet(v);
向量v將通過引用傳遞。但是,如果使用右值調用它:
perfectSet(makeAndFillVector());
(匿名)向量將通過右值引用傳遞。因此,故意設置C ++ 11魔術以盡可能保留參數的右值性質。
現在,在perfectSet內部,您希望將參數完美地傳遞給的正確重載set()。這std::forward是必需的:
template<class T>
void perfectSet(T && t) {
set(std::forward<T>(t));
}
如果沒有std :: forward,編譯器將不得不假定我們要通過引用傳遞t。為了使自己確信這是真的,請比較以下代碼:
void perfectSet(T && t) {
set(t);
set(t); // t still unchanged
}
對此:
void perfectSet(T && t) {
set(std::forward<T>(t));
set(t); // t is now empty
}
如果您未明確轉發t,則編譯器必須防御性地假設您可能會再次訪問t并選擇set的左值引用版本。但是,如果轉發t,則編譯器將保留其右值性,并且set()將調用的右值引用版本。此版本移動的內容t,這意味著原始內容為空。
這個答案比我最初認為的要長得多;-)
- 3 回答
- 0 關注
- 650 瀏覽
添加回答
舉報